2025.05.11
【Next】Laravel、Next(TypeScript)、MySQLで掲示板作成チュートリアル

Image by AGNIESZKA WEN from Pixabay

はじめに

・フロントエンド:Next.js(TypeScript)
・バックエンド:PHP(Laravel)
を使って、簡単な掲示板をつくります。

 

最低限CRUDができる機能のみの実装で、
デザインは一切せず、とにかくシンプルにしています。

 

以下が完成イメージです。

動作確認した環境

OS:Windows11 Home
   WSL2(Ubuntu 20.04.6)
PHP:8.4.7
Laravel:12.10.2
Node:22.15.0
npm:10.9.2
Next:15.3.1

プロジェクトの作成

ディレクトリの構成は以下の様にします。

myapp:親ディレクトリ

├─backend:Laravelのプロジェクト
└─frontend:Nextのプロジェクト

親ディレクトリを作成

任意の場所に「myapp」ディレクトリを作成してください。

Next.jsプロジェクトの作成

myappディレクトリにcdコマンドで移動して、以下コマンドを実行します。

npx create-next-app frontend --typescript

 

コマンド実行時に質問されるのですが、今回は全てデフォルトで設定します。

Would you like to use ESLint? … No / Yes ⇒Yes
Would you like to use Tailwind CSS? … No / Yes ⇒No
Would you like your code inside a src/ directory? … No / Yes ⇒Yes
Would you like to use App Router? (recommended) … No / Yes ⇒Yes
Would you like to use Turbopack for next dev ? … No / Yes ⇒No
Would you like to customize the import alias (@/* by default)? … No / Yes ⇒No

Laravelプロジェクトの作成

myappディレクトリにcdコマンドで移動して、以下コマンドを実行します。

composer create-project --prefer-dist laravel/laravel backend

バックエンドのセットアップ

次に、Laravelの設定をします。

.envの編集

myapp/backend/.envに以下の通り1行追記します。

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:eLWHYmxf1vsK6uKmajF5SFQ4w1HsBNOmZdrEZo4bZ1c=
APP_DEBUG=true
APP_URL=http://localhost
APP_FRONTEND_URL=http://localhost:3000  #追記

~~省略~~

 

app.phpの編集

myapp/backend/config/app.phpに以下の通り1行追記します。

~~省略~~

'url' => env('APP_URL', 'http://localhost'),

'frontend_url' => env('APP_FRONTEND_URL', 'http://localhost'),  //追記

~~省略~~

cors.phpの編集

1.以下のコマンドでcors.phpを作成する

/myapp/backendにcdコマンドで移動して、以下コマンドを実行してください。

php artisan config:publish cors

 

2./myapp/backend/config/cors.phpを以下の様に編集します。

~~省略~~

'paths' => ['api/*', 'sanctum/csrf-cookie'],

//許可するメソッド(GET、POSTなど)
'allowed_methods' => ['*'],

//APIの使用を許可する接続元
'allowed_origins' => [config('app.frontend_url')],  //変更

'allowed_origins_patterns' => [],

'allowed_headers' => ['*'],

'exposed_headers' => [],

'max_age' => 0,

'supports_credentials' => true,     //trueに変更

~~省略~~

APIの作成

1.api.phpを作成する

/myapp/backendにcdコマンドで移動して、以下コマンドを実行してください。

php artisan install:api

 

2./myapp/backend/routes/api.phpに以下3行を追記してください。

~~省略~~

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

//以下を追記
Route::get('/sample', function(){
  
  // APIの戻り値はJSON型が基本
  return response()->json([
    'text' => 'サンプルデータ'
  ]);
});

 

3.動作確認

cdコマンドで/myapp/backendに移動し、以下コマンドを実行してWebサーバーを起動してください。

php artisan serve

 

ブラウザで「http://localhost:8000/api/sample」にアクセスし、
画面左上に以下のように表示されていればOKです。

{"text":"\u30b5\u30f3\u30d7\u30eb\u30c7\u30fc\u30bf"}

エスケープされているので、何が書いてあるのかよくわかりませんが、

{”text”: ~~}

の形式で表示されていれば問題ありません。

フロントエンドのセットアップ

Next.js側の設定をします。

必要なパッケージのインストール

コマンドラインでcdコマンドにて/myapp/frontに移動し、
以下コマンドを実行してください。

npm install swr

swrはAjax通信処理の周辺処理を簡潔に書くための便利なパッケージです。

fetcher関数の作成

次にAjax通信をする関数を作成します。

 

myapp/frontend/src/libs/fetcher.tsとなるようフォルダ、ファイルを作成し、
fetcher.tsに以下を記述してください。

 

(JSXを書かないファイルの拡張子「.ts」で作成します。)

// 共通のAjax通信処理「fetcher」を作成
const fetcher = async (
  // 以下はfetcherの引数
  url: string,                                  // 通信先のURL
  methodVal: 'GET' | 'POST' | 'PUT' | 'DELETE', // HTTPリクエストメソッド
  body?: any                                    // POST,PUTの場合のHTTPリクエストボディの値
) => {

  // BackendのURL
  const BackendUrl = "http://localhost:8000";

  // 通信処理
  const response = await fetch(`${BackendUrl}/${url}`, {
    method: methodVal,        // HTTPリクエストメソッドを設定
    credentials: 'include',   // Cookieも含めて送る

    // HTTPリクエストのヘッダー情報を設定
    headers: {
      'Content-Type': 'application/json',
      'X-Requested-With': 'XMLHttpRequest',         // Ajax通信であることを伝える
    },
    body: body ? JSON.stringify(body) : undefined,  // bodyの設定
  });

  // 通信エラーの場合の処理
  if(!response.ok){
    throw new Error("Ajax通信でエラーが発生しました。");
  }

  // 取得したデータをJSON形式に変換して渡す
  return response.json();
}

export default fetcher;

ChatGPTに聞いてみると、
外部ライブラリの「axios」よりブラウザ標準機能のfetchが今は人気とのことで、

今回はfetchを使用しました。

画面をつくる

myapp/frontend/src/app/page.tsxのコードを全て削除し、以下を記述します

'use client'

import styles from "./page.module.css";
import { useState } from "react";
import useSWR from "swr";
import fetcher from "@/libs/fetcher";

export default function Home(){

  // Ajax通信でデータを取得(データ取得後、自動で再レンダリング)
  const {data, error} = useSWR('api/sample', fetcher);

  // 通信でエラー発生した時の処理
  if(error) return <div>エラーが発生しました。</div>

  // 「dataが無い=通信途中」の時の処理
  if(!data) return <div>読み込み中</div>

  return (
    <div>
      <h1>NextでLaravelから取得したデータ:{data.text}</h1>
    </div>
  )
}

APIの動作確認

LaravelとNextが通信できているかを確認します。

新規でコマンドラインを開き、
cdコマンドにて/myapp/frontendに移動して、
以下コマンドでWebサーバーを起動してください。

npm run dev

※コマンドラインは必ず新規で開いてください。
 (Laravelも起動したままにする必要があります。)

 

ブラウザで「http://localhost:3000」にアクセスして画面左上に

と表示されていればOKです。

掲示板をつくる:バックエンド側の作業

LaravelとNextのデータの受け渡しはできたので、掲示板機能のバックエンド側をつくっていきます。

DBの接続設定

myapp/backend/.envを以下の様に編集します。

~~省略~~

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myapp_db         #先程作成したDBを使用する
DB_USERNAME=MySQLのユーザ名   #ご自身の環境に合わせて記入
DB_PASSWORD=MySQLのパスワード #ご自身の環境に合わせて記入

~~省略~~

APIルートの設定

myapp/backend/routes/api.phpに以下を追記してください。

use App\Http\Controllers\PostController;  // Postコントローラの読み込み

~~省略~~

// 投稿の取得
//「/posts」にgetでアクセス来たらPostコントローラのIndexメソッドを実行
Route::get('/posts', [PostController::class, 'index']);

// 投稿作成
Route::post('/posts', [PostController::class, 'store']);

// 投稿の編集
Route::put('/posts/{id}', [Postcontroller::class, 'update']);

// 投稿の削除
Route::delete('/posts/{id}', [PostController::class, 'destroy']);

PostモデルとPostコントローラの作成

myapp/backendにcdコマンドで移動し、以下コマンドを実行してください。

php artisan make:model Post -mc

-m、-cオプションをつけてモデルと一緒に「マイグレーションファイル」と「コントローラ」も作成します。

 

以下の3ファイルが作成されます。
・app/Models/Post.php
・database/migrations/20xx_xx_xx_032058_create_posts_table.php  
・app/Http/Controllers/PostController.php

マイグレーションファイルを編集する

Postテーブルに必要なカラムをマイグレーションファイルで定義します。

 

myapp/backend/database/migrations/database/migrations/20xx_xx_xx_032058_create_posts_table.php
に以下の通り1行追記します。

~~省略~~

public function up(): void
{                  // ↓テーブル名
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('text')->default('入力無し');  //追記
        $table->timestamps();
				//$table->カラムのデータ型(カラム名)->default(デフォルト値);
    });
}

~~省略~~

マイグレーションを実行してPostテーブル作成

myapp/backendにcdコマンドで移動し、以下コマンドを実行してください。

php artisan migrate

 

コマンドを実行すると、
「DBもまだ作成されてないけど作りますか?」
と確認されるので、Yesで進めます。

WARN  The database ‘myapp_db' does not exist on the 'mysql' connection. 
Would you like to create it?()

 

↓こんな感じで「DONE」が表示されればOKです。
(行数は場合によって変わるので気にしないで大丈夫です。)

これで、マイグレーションファイルに記述した内容でテーブルが作成されます。

Postコントローラの編集

myapp/backend/app/Http/Controllers/PostController.phpを以下のように編集します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Post; //Postモデルを読み込む

class PostController extends Controller
{
    // 投稿を一覧表示させる画面用の処理
    public function index(){
    
      // json形式の文字列でPostテーブルの全レコードを返す
      return response()->json(Post::all());
      
      // 以下でもOK!(Laravelは配列,コレクションをreturnすると自動でjsonに変換してくれる。他メソッドも同様です。)
      // return Post::all();  
    }                        

    // 投稿をテーブルに登録する画面用の処理
    public function store(Request $request){
                              
        // textカラム用の値だけ取得して登録(textカラム以外編集させない)
        return response()->json(Post::create($request->only(['text']))); 
    }                                                           
    public function update(Request $request, $id){
      // 投稿のidで編集対象のレコードを取得
      $post = Post::findOrFail($id);
      
      // textカラムの値を書き換える
      $post->text = $request->input('text');
      
      // DBに保存
      $post->save();

      return response()->json($post);
    }

    public function destroy($id){
      // 投稿のidで編集対象のレコードを取得
      $post = Post::findOrFail($id);
      
      // 投稿削除
      $post->delete();

      return response()->json(['message' => '投稿を削除しました']);
    }        
}

Postモデルの編集

myapp/backend/app/Models/Post.phpを以下の様に編集してください。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // textカラム以外編集させない
    protected $fillable = ['text']; //追記
}

掲示板をつくる:フロントエンド側の作業

ページの作成

/frontend/src/app/post/page.tsxを作成し、以下記述してください。

'use client';

import React, { useState } from 'react';
import useSWR, { mutate } from 'swr';
import fetcher from '@/libs/fetcher';

export default function PostPage() {
    const [input, setInput] = useState<string>('');

    // 投稿一覧を取得(GET)
    const { data, error } = useSWR<any[]>('api/posts', (url:string) => fetcher(url, 'GET'));

    // 投稿を作成(POST)
    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        if(!input.trim()) return

        try{
          await fetcher(`api/posts`, 'POST', { text: input})  // POSTリクエスト
          setInput('')                                        // 入力欄をクリア
          mutate('api/posts') // SWRキャッシュの再取得(一覧を更新)
        }catch(error){
          console.error('投稿エラー:', error)
        }
    };

    // 投稿を編集(PUT)
    const handleEdit = async (post: any) => {
      const newText = prompt('更新後の内容を入力してください', post.text);
      if(!newText) return;

      try {
        await fetcher(`api/posts/${post.id}`, 'PUT', { text: newText});
        mutate('api/posts');
      }catch(error){
        console.error('更新エラー', error);
      }
    };

    // 投稿を削除(DELETE)
    const handleDelete = async(post: any) => {
      if(!confirm('本当に削除しますか?')) return;

      try{
        await fetcher(`api/posts/${post.id}`, 'DELETE');
        mutate('api/posts');
      }catch(error){
        console.error('削除エラー', error);
      }
    };

    return (
        <div>
            <h1>掲示板</h1>
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                />
                <button type="submit">投稿</button>
            </form>
            <ul>
                {data?.map((post) => (
                    <li key={post.id}>

                        {/* 投稿の表示 */}
                        {post.text}:{post.created_at}

                        {/* 編集ボタン */}
                        <button onClick={() => handleEdit(post)}>編集</button>
                        
                        {/* 削除ボタン */}
                        <button onClick={() => handleDelete(post)}>削除</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

動作確認

1)Laravelサーバーの起動:   Laravelプロジェクトのルートで以下のコマンドを実行します。

php artisan serve

 

2)Next.jsアプリケーションの起動:   Next.jsプロジェクトのルートで以下のコマンドを実行します。

npm run dev

 

3)ブラウザで「localhost:3000/post」にアクセスし、動作確認してください。

以下のように表示、投稿ができるかと思います。

 

これで、Next.jsとLaravelを使用して簡単な掲示板アプリの作成完了です。

 

以上です。