DosanKongCode

ChatGPTと共にNext.jsで簡易的な掲示板アプリの作る.

Published on
Authors

この記事ではChatGPTを利用して、React、Typescript、そしてNext.jsを使って掲示板アプリを作成する方法について説明します。

この記事を作成するにあたって作成したプロンプトは下記のようになります。

次で与えられたテーマ、技術、目次で技術記事を書いてください。

テーマ
- Next.jsで掲示板アプリの作り方

技術
- React
- Typescript
- Next.js

また、記事を書く際には、以下のルールを必ず遵守してください。
- フォーマットはマークダウンで記載すること
- できるだけ最新の技術を用いること
- 記事内ではソースコードを多めに書くこと
- ソースコードの説明をすること

目次
- 掲示板アプリとは何か?
- 環境の設定
- フロントエンドの作成
  - コンポーネントの作成
  - ページの作成
- APIの作成
  - バックエンドの作成
  - APIの作成
  - APIのテスト
- データベースの作成
- バックエンドとフロントエンドの連携
  - データの取得
  - データの投稿
- まとめ

環境の設定

まず最初に、以下のコマンドを使用して、Next.jsプロジェクトを作成します。

npm create-next-app bulletin-app

その後、必要な依存関係をインストールします。

npm install react react-dom next typescript

フロントエンドの作成

コンポーネントの作成

まず、掲示板アプリのフロントエンドで使用するコンポーネントを作成します。ここでは、componentsというディレクトリを作成し、その中にMessageListというコンポーネントを作成します。MessageListコンポーネントは、メッセージのリストを表示するためのものです。

import React from 'react';

interface Props {
  messages: string[];
}

const MessageList: React.FC<Props> = ({ messages }) => (
  <ul>
    {messages.map((message) => (
      <li key={message}>{message}</li>
    ))}
  </ul>
);

export default MessageList;

ページの作成

次に、pagesディレクトリに、index.tsxというファイルを作成します。index.tsxファイルは、掲示板アプリのトップページとして機能します。以下のようなコードを追加します。

import React, { useState } from 'react';
import MessageList from '../components/MessageList';

const HomePage: React.FC = () => {
  const [messages, setMessages] = useState<string[]>([]);

  const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    const message = formData.get('message') as string;
    setMessages((prevMessages) => [...prevMessages, message]);
  };

  return (
    <div>
      <form onSubmit={handleFormSubmit}>
        <input name="message" type="text" />
        <button type="submit">Post Message</button>
      </form>
      <MessageList messages={messages} />
    </div>
  );
};

export default HomePage;

このページでは、ユーザーがメッセージを投稿できるフォームを表示し、投稿されたメッセージのリストをMessageListコンポーネントで表示しています。

APIの作成

バックエンドの作成

次に、掲示板アプリのバックエンドを作成します。まず、apiというディレクトリを作成し、その中にmessages.tsというファイルを作成します。このファイルでは、メッセージを管理するためのAPIを実装します。

let messages: string[] = [];

export const getMessages = (): string[] => {
  return messages;
};

export const addMessage = (message: string): void => {
  messages.push(message);
};

messages.tsファイルでは、messagesという配列にメッセージを格納し、getMessages関数でメッセージの一覧を返し、addMessage関数で新しいメッセージを配列に追加します。

APIの作成

次に、pages/apiディレクトリに、messages.tsというファイルを作成します。このファイルでは、GETリクエストに対応するgetMessages関数と、POSTリクエストに対応するaddMessage関数を実装します。

import { NextApiRequest, NextApiResponse } from 'next';
import { addMessage, getMessages } from '../../api/messages';

export default (req: NextApiRequest, res: NextApiResponse) => {
  switch (req.method) {
    case 'GET':
      res.status(200).json(getMessages());
      break;
    case 'POST':
      const { message } = req.body;
      addMessage(message);
      res.status(200).send('Message added successfully');
      break;
    default:
      res.status(405).end();
      break;
  }
};

このファイルでは、switch文を使用して、GETリクエストとPOSTリクエストを処理します。GETリクエストの場合は、getMessages関数を呼び出してメッセージの一覧を返し、POSTリクエストの場合は、リクエストボディからメッセージを取得し、addMessage関数で配列に追加します。

データベースの作成

ここまでで、APIの実装が完了しました。しかし、現在の実装では、メッセージがサーバーのメモリに格納されているため、サーバーを再起動すると、メッセージが消えてしまいます。そこで、データベースを使って、メッセージを永続化するように改善しましょう。

まず、postgresというデータベースを使用するために、pgというパッケージをインストールします。

npm install pg
npm install --save-dev @types/pg

次に、.env.localファイルを作成し、そのファイル内にデータベースに接続するための情報を設定します。

DATABASE_URL=postgresql://postgres:password@localhost:5432/nextjs_board

ここで、DATABASE_URLには、postgresql://ユーザー名:パスワード@ホスト名:ポート番号/データベース名という形式で、データベースに接続するためのURLを指定します。

次に、lib/db.tsというファイルを作成し、データベースに接続するための関数を実装します。

import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production',
});

export const query = (text: string, params?: any[]) => {
  return pool.query(text, params);
};

このファイルでは、pg.Poolを使用して、データベースに接続します。process.env.DATABASE_URLを使用して、.env.localファイルで設定したURLを取得します。また、sslオプションをtrueに設定することで、本番環境ではSSLを使用して通信するようにします。

メッセージの永続化

次に、messages.tsファイルを修正して、データベースにメッセージを保存するようにします。

import { query } from "../lib/db"
import Message from "./message";

export const getMessages = async (): Promise<Message[]> => {
    const result = await query('SELECT * FROM messages');
    const messages: Message[] = []

    for (var i = 0; i < result.rows.length; i++) {
        messages.push({
            id: i,
            message: result.rows[i].message,
            date: result.rows[i].timestamp,
            userName: result.rows[i].user_name,
        })
    }
    return messages.sort((a, b) => a.id > b.id ? -1 : 0);
};

export const addMessage = async (message: string, userName: string): Promise<void> => {
    const timestamp = new Date().toLocaleString();
    await query('INSERT INTO messages VALUES ($1, $2, $3)', [message, userName, timestamp]);
};

その際に、下記のmessage.tsファイルを追加します。

export default interface Message {
    id: number;
    message: string;
    date: string;
    userName: string;
}

このファイルでは、query関数を使用して、messagesテーブルからメッセージを取得するgetMessages関数と、messagesテーブルにメッセージを挿入するaddMessage関数を実装しています。

バックエンドとフロントエンドの連携

ここまでで、バックエンドのAPIと、データベースの設定が完了しました。次に、フロントエンドとバックエンドを連携するためのコードを書いていきます。

まず、pages/api/messages/index.tsを以下のように修正します。

import { NextApiRequest, NextApiResponse } from 'next';
import { addMessage, getMessages } from '../../../lib/messages';

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    const messages = await getMessages();
    res.status(200).json(messages);
  } else if (req.method === 'POST') {
    const message = req.body.message;
    await addMessage(message);
    res.status(200).send('Message added successfully');
  }
}

このファイルでは、getMessages関数とaddMessage関数をインポートし、GETリクエストとPOSTリクエストに対して、適切なレスポンスを返します。

次に、pages/index.tsxを以下のように修正します。

import Message from '@/api/message';
import { useState, useEffect } from 'react';
import styles from '../styles/Home.module.css';

interface Post {
  userName: string;
  content: string;
  postTime: string;
}

const Home = () => {
  const [messages, setMessages] = useState<Message[]>([]);
  const [userName, setUserName] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    fetch('/api/messages')
      .then((res) => res.json())
      .then((data) => setMessages(data));
  }, []);

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (content.length < 1 || userName.length < 1) {
      return;
    }
    fetch('/api/messages', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        message: content,
        userName: userName,
      }),
    })
      .then((res) => res.text())
      .then((data) => {
        setContent('');
        setUserName('');

        fetch('/api/messages')
          .then((res) => res.json())
          .then((data) => setMessages(data));
      });
  };

  const handleUserNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setUserName(event.target.value);
  };

  const handleContentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
    setContent(event.target.value);
  };

  return (
    <div className={styles.container}>
      <h1>掲示板</h1>
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label htmlFor="userName">ユーザー名</label><br />
          <input type="text" className="form-control" id="userName" value={userName} onChange={handleUserNameChange} required />
        </div>
        <div className="form-group">
          <label htmlFor="content">投稿内容</label><br />
          <textarea className="form-control" id="content" value={content} onChange={handleContentChange} required />
        </div>
        <button type="submit" className="btn btn-primary">投稿する</button>
      </form>
      <div className="post-list">
        {messages.map((data, index) => (
          <div className="post" key={index}>
            <div className="post-header">
              <h2>{data.userName}</h2>
              <p>{data.date}</p>
            </div>
            <div className="post-content">
              <p>{data.message}</p>
            </div>
          </div>))}
      </div>
    </div>
  );
};

export default Home;

このファイルでは、useStateフックを使用して、messagesnewMessageという状態を管理しています。useEffectフックを使用して、ページがロードされたときに/api/messagesGETリクエストを送信し、取得したメッセージをmessagesステートにセットしています。

handleNewMessage関数は、新しいメッセージを送信するために使用されます。フォームが送信されたときに、/api/messagesPOSTリクエストを送信して、新しいメッセージを追加します。リクエストが成功した場合、setNewMessage関数を使用して、入力フィールドをクリアし、/api/messagesGETリクエストを送信して、更新されたメッセージリストを取得し、messagesステートにセットします。

まとめ

この記事では、Next.jsを使用して掲示板アプリを作成する方法を説明しました。React、TypeScript、およびNext.jsを使用して、フロントエンドとバックエンドを構築し、データベースとの通信を設定しました。最後に、Vercelを使用してアプリをデプロイしました。

この記事を通じて、Next.jsの基本的な概念と、ReactとTypeScriptの基礎を学ぶことができました。Next.jsは、Reactアプリケーションの構築に役立つ強力なフレームワークであり、高速な開発サイクルを実現することができます。

Loading Giscus Discussion