使い慣れたLaravelをバックエンドに利用してフロントエンドにはReactのフレークワークNext.jsを使ってアプリケーションを構築したいという人向けの内容になっています。LaravelとNext.jsを個別にスクラッチから構築を行うのではなくLaravel側ではLaravel Breezeが提供するAPIオプション、Next.jsではLaravel開発者がメンテナンスを行なっているLaravel Breeze Next.js Editionを利用します。Breezeを利用することでLaravel Sanctumが組み込まれた認証機能が事前に実装されているので認証機能が実装された状態から開発を開始することができます。

本文書ではBreezeを利用したLaravel, Next.jsの環境の構築方法とどのように認証機能が実装されているか簡単に説明を行っています。

LaravelでBreezeを使ってReact, Vueを利用したい場合にInertiaやLivewireを利用しないといけないのかなと思う人もいるかもしれませんが今回の構成ではInertiaやLivewireは利用していません。

バックエンドの設定

バックエンドにはLaravelを利用するのでLaravelプロジェクトの作成を行います。Laravelプロジェクトを作成後にLaravel Breezeパッケージのインストールを行います。

Laravelプロジェクトの作成

Laravelをインストールを行う前にNext.jsをインストールするフロントエンド用のフォルダとバックエンド用のLaravelのフォルダをわけるために任意の場所にlaravel9フォルダを作成します。

laravel9フォルダを作成後にlaravel9フォルダに移動してlaravelコマンドを利用してLaravelプロジェクトの作成を行います。


 % laravel new laravel-backend

Breezeのインストール

プロジェクトの作成が完了するとlaravel-backendフォルダに移動してcomposeコマンドを利用してbreezeパッケージのインストールを行います。


 % cd laravel-backend
 % composer require laravel/breeze --dev

breezeのインストールが完了するとphp artisan breezeコマンドで利用できるようになります。

api用のscaffoldingの作成

php artisan breeze:install apiコマンドを利用することでAPI用のScaffolding(骨組み)がインストールされます。


 % php artisan breeze:install api
Breeze scaffolding installed successfully.

artisanコマンドを実行すると.envファイルに環境変数のFRONTEND_URLが設定されます。


FRONTEND_URL=http://localhost:3000

フロントエンドとバックエンドのオリジンが異なっていても通信が行えるようにCORS(Cross-Origin Resource Sharing)の設定も行われます。configフォルダのcors.phpファイルを確認すると.envファイルに設定したFRONTEND_URLがallowed_originsの配列に含まれていることが確認できます。


return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */

    'paths' => ['*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => [env('FRONTEND_URL')],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => true,

];

breeze:install apiによってその他にどのようなファイルの更新が行われているかを確認するのはGithubのlaravel/breezeのソースコードのInstallsApiStack.phpを見ることで確認することができます。

認証に関するURLはroutesフォルダのauth.phpファイルで確認することができます。

データベースの作成

認証を実装するためにはユーザデータをデータベースに保存する必要があります。データベースは簡易的に利用することができるsqliteを利用します。sqliteを利用するためにdatabaseフォルダにdatabase.sqliteファイルを作成します。


 % touch database/database.sqlite

作成後は.envファイルを開いてデフォルトのデータベースであるmysqlからsqliteへ変更を行います。先頭にDB_がついているものの中からDB_CONNECTION以外の環境変数を削除します。削除後にDB_CONNECTIONにsqliteを設定します。


DB_CONNECTION=sqlite

設定完了後テーブルを作成するためにphp artisan migrateコマンドを実行します。実行するとデータベースには4つのテーブルが作成されます。


 % php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (3.64ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (1.77ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (1.65ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (2.39ms)

開発サーバの起動

php artisan serveコマンドを実行します。http://127.0.0.1:8000で起動します。


% php artisan serve
Starting Laravel development server: http://127.0.0.1:8000
[Sat Mar 12 20:44:00 2022] PHP 8.0.7 Development Server (http://127.0.0.1:8000) starte

ブラウザでhttp://127.0.0.1:8000にアクセスすると通常はLarvelの初期画面が表示されますがBreezeのAPIを利用している場合はLaravelのバージョンがJSONで戻されます。

breeze apiを利用した場合のルートへのアクセス
breeze apiを利用した場合のルートへのアクセス

ルーティング設定ファイルのweb.phpを確認するとLaravelバージョンを戻す設定が行われていることが確認できます。


Route::get('/', function () {
    return ['Laravel' => app()->version()];
});

フロントエンドの設定

フロントエンドにはNext.jsを利用します。Githubで公開されているbreeze-nextのリポジトリをcloneしてNext.jsの環境を作成します。breeze-nextリポジトリからインストールするNext.jsにはLaravel Sanctumによる認証機能が実装されています。

リポジトリのクローン

breeze-nextのリポジトリをクローンするためにgit cloneコマンドを実行します。別名にはnextjs-frontendという名前をつけています。


 % git clone https://github.com/laravel/breeze-next.git nextjs-frontend
Cloning into 'nextjs-frontend'...
remote: Enumerating objects: 110, done.
remote: Counting objects: 100% (25/25), done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 110 (delta 5), reused 14 (delta 4), pack-reused 85
Receiving objects: 100% (110/110), 148.40 KiB | 8.24 MiB/s, done.
Resolving deltas: 100% (23/23), done.

npm installの実行

git cloneコマンドを実行するとnextjs-frontendフォルダが作成されるのでnextjs-frontendフォルダに移動し依存関係を持つJavaScriptのライブラリをインストールするためにnpm installコマンドかyarn installコマンドを実行します。ここではnpm installコマンドを実行します。


 % cd nextjs-frontend
 % npm install

環境変数を設定する.env.exampleファイルがnextjs-frontendフォルダの下に作成されているのでコピーして.env.localファイルを作成します。

開発サーバの起動

npm run devコマンドを実行して開発サーバを起動します。php artisan serveではLaravelの開発サーバを起動しここではNext.jsの開発サーバを起動しています。


 % npm run dev

> breeze-next@0.1.0 dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Loaded env from /Users/mac/Desktop/laravel9/nextjs-frontend/.env.local
event - compiled client and server successfully in 378 ms (127 modules)

http://localhost:3000にブラウザからアクセスするとLaravelの初期画面が表示されます。これはバックエンドのLaravelの中にあるファイルによって表示されているのではなくNext.jsの中のファイルを利用して表示されています。後ほどどのファイルの内容が表示されているのか確認します。

Next.jsを利用して表示されるLareavelの初期画面
Next.jsを利用して表示されるLareavelの初期画面

バックエンドに起動しているLaravelの開発サーバを停止しても上記のページは表示され続けます。

ここまでの作業でバックエンドのLaravelとフロントエンドのNext.jsの環境構築は完了です。

認証の確認

breeze-nextのリポジトリにはLaravelを利用する認証に必要な実装がすでに行われています。

ユーザの登録

構築した環境を利用してユーザの登録を行います。動作確認を行う場合はフロントエンドのNext.jsとバックエンドのLaravelを起動しておく必要があります。

初期画面の右上のRegisterのリンクをクリックして登録したユーザ情報を入力します。

ユーザの登録画面
ユーザの登録画面

ユーザの登録が完了するとダッシュボード画面が表示されます。

ダッシュボード画面
ダッシュボード画面

Laraveのデータベースにユーザが登録されているか確認するためにtinkerを利用します。作成したユーザがusersテーブルに登録されていることを確認することができます。Next.jsで表示されているユーザ登録画面からLaravel側のテーブルにデータが挿入されることがわかりました。


% php artisan tinker
Psy Shell v0.11.2 (PHP 8.0.7 — cli) by Justin Hileman
>>> $users = User::all();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> Illuminate\Database\Eloquent\Collection {#4243
     all: [
       App\Models\User {#4205
         id: "1",
         name: "John Doe",
         email: "john@example.com",
         email_verified_at: null,
         #password: "$2y$10$.1ZucYhNLaS4eb2T/CeILO.FkmuNnAot66TaRWgm0v7yr3b4k321m",
         #remember_token: null,
         created_at: "2022-03-12 12:12:24",
         updated_at: "2022-03-12 12:12:24",
       },
     ],
   }
>>> 

初期画面の表示までの流れ

Next.jsではlocalhost:3000にブラウザからアクセスするとpagesフォルダにあるindex.jsが実行されます。コードの量が多いので一部のみ記載しますがhookのuseAuthを利用してuser情報の取得を行っています。user情報が取得できた(ログインしている状態)か取得できなかったかによって表示される内容が変わります。


import Head from 'next/head'
import Link from 'next/link'
import { useAuth } from '@/hooks/auth'

export default function Home() {
    const { user } = useAuth({ middleware: 'guest' })

    return (
        <>
            <Head>
                <title>Laravel</title>
            </Head>

            <div className="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">
                <div className="hidden fixed top-0 right-0 px-6 py-4 sm:block">
                    {user ?
                        <Link href="/dashboard">
                            <a className="ml-4 text-sm text-gray-700 underline">
                                Dashboard
                            </a>
                        </Link>
                        :
                        <>
                            <Link href="/login">
                                <a className="text-sm text-gray-700 underline">Login</a>
                            </Link>

                            <Link href="/register">
                                <a className="ml-4 text-sm text-gray-700 underline">
                                    Register
                                </a>
                            </Link>
                        </>
                    }
                </div>

ログインしていない場合はuser情報を使った分岐によって画面の右上上部にRegisterとLoginのリンクが表示されます。

Next.jsを利用して表示されるLareavelの初期画面
Next.jsを利用して表示されるLareavelの初期画面

ログインしている場合には右上にはDashboardへのリンクが表示されます。

ログイン後はDashboardへのリンクが表示
ログイン後はDashboardへのリンクが表示

useAuth Hook

Githubのbreeze-nextリポジトリを利用して構築したNext.js全体を理解するためには認証機能の中心的な役割を果たすuseAuth Hookを理解する必要があります。

useAuth Hookが含まれているhooks/auth.jsファイルを確認するとregisterやlogin関数も設定されていますがuseSWR Hookを利用してバックエンドの/api/userにアクセスを行いユーザ情報の取得を行っています。useSWRはデータ取得のための React HooksライブラリSWRのHookで第一引数にキーを設定して第2引数に関数を指定することができ第2引数の関数が実行されます。状態管理、データをキャッシュしたり関数を再実行することができる便利なHookです。useSWRを利用しない場合はuseStateで状態を定義してuseEffectでデータを取得するといった処理が必要でありますが下記のコードを見てわかるようにuseSWRには状態管理などの機能が含まれています。


import useSWR from 'swr'
import axios from '@/lib/axios'
import { useEffect } from 'react'
import { useRouter } from 'next/router'

export const useAuth = ({ middleware, redirectIfAuthenticated } = {}) => {
    const router = useRouter()

    const { data: user, error, revalidate } = useSWR('/api/user', () =>
        axios
            .get('/api/user')
            .then(res => res.data)
            .catch(error => {
                if (error.response.status !== 409) throw error

                router.push('/verify-email')
            }),
    )
    //略

useAuth Hookを利用することで取得するユーザ情報でログインしているかどうかチェックすることができます。新しく作成したページにユーザがログインしているかどうかによるアクセス制限を行いたい場合のuseAuth Hookの設定方法について確認します。

新規ページでuseAuth Hook

Next.jsではpagesフォルダにファイルを作成することで自動でルーティングが設定されるので新しいページを追加するためexample.jsファイルをpagesフォルダの下に作成します。

useAuthを利用し下記の設定を行うことでページにアクセスした時にユーザのログイン状態がチェックされます。ユーザがログインしている場合に/exampleにアクセスするとユーザ名とログアウトボタンが表示され、ログインしていない場合には/loginページにリダイレクトさせることができます。


import { useAuth } from '@/hooks/auth'

const Example = () => {
    const { logout, user } = useAuth({ middleware: 'auth' })

    return (
        <>
            <p>{user?.name}</p>

            <button onClick={logout}>Sign out</button>
        </>
    )
}

export default Example

ログイン後に表示されるDashboardのページにもuseAuth Hookが利用されているので確認しておきましょう。Dashboardページの内容はpagesフォルダのdashboard.jsファイルに記述されています。


import AppLayout from '@/components/Layouts/AppLayout'
import Head from 'next/head'

const Dashboard = () => {
    return (
        <AppLayout
            header={
                <h2 className="font-semibold text-xl text-gray-800 leading-tight">
                    Dashboard
                </h2>
            }>

            <Head>
                <title>Laravel - Dashboard</title>
            </Head>

            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                        <div className="p-6 bg-white border-b border-gray-200">
                            You're logged in!
                        </div>
                    </div>
                </div>
            </div>
        </AppLayout>
    )
}

export default Dashboard

上記を見るとどこにもuseAuth Hookを見つけることができませんがレイアウトファイルであるAppLayout.jsファイルを確認するとuseAuth Hookの設定を確認することができます。


import Navigation from '@/components/Layouts/Navigation'
import { useAuth } from '@/hooks/auth'

const AppLayout = ({ header, children }) => {
    const { user } = useAuth({ middleware: 'auth' })
    //略

ユーザ一覧を表示

ここまでの理解ができていればログインしたユーザのみ/usersにアクセスするとユーザ一覧を表示するページを簡単に作成することができます。

ユーザ一覧をLaravelのバックエンドから取得できるようにapi.phpファイルにルーティングを追加します。


Route::middleware(['auth:sanctum'])->get('/users', function () {
    return \App\Models\User::all();
});

フロントエンドのNext.js側ではdashboard.jsファイルを元にpagesフォルダにusers.jsファイルを作成します。データの取得はuseSWRを利用します。


import AppLayout from '@/components/Layouts/AppLayout'
import Head from 'next/head'
import useSWR from 'swr'
import axios from '@/lib/axios'

const Users = () => {
    const { data: users, error } = useSWR('/api/users', () =>
        axios
            .get('/api/users')
            .then(res => res.data)
            .catch(error => {
                console.error(error)
            }),
    )

    if (error) return 'An error has occurred.'

    return (
        <AppLayout
            header={
                <h2> className="font-semibold text-xl text-gray-800 leading-tight">
                    Users
                </h2>
            }>
            <Head>
                <title>Laravel - Users</title>
            </Head>

            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                        <div> className="p-6 bg-white border-b border-gray-200">
                            <ul>
                                {users?.map(user => (
                                    <div> key={user.id}>{user.name}</div>
                                ))}
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </AppLayout>
    )
}

export default Users

ブラウザで確認するとユーザが1名しか追加されていないので下記のように表示されます。ログインしていない場合に/usersにアクセスするとログイン画面にリダイレクトされます。

ユーザ情報の取得
ユーザ情報の取得

別のブラウザを起動してユーザを追加し先ほど”John Doe”と表示されていたブラウザに再度フォーカスするとuseSWR Hookにより追加したユーザが表示されます。

追加したユーザが自動で表示される
追加したユーザが自動で表示される

Breeze apiとbreeze-nextリポジトリを利用することで認証機能が実装されたアプリケーションを構築できることが理解できました。