この連載記事では、フロントエンドに Vue.js + Vue Router + Vuex とサーバーサイドに Laravel を使用したシングルページ Web アプリケーションの開発方法を紹介します。実際に写真共有アプリを開発する手順を通して SPA 開発のエッセンスを学ぶことができます。
今回のチュートリアルで扱うツールなどのバージョンは以下の通りです。
Node | npm | Vue.js | Vue Router | Vuex | PHP | Laravel |
---|---|---|---|---|---|---|
10.15 | 6.4 | 2.6 | 3.1 | 3.1 | 7.4 | 6.9 |
この章から認証機能を実装していきます。
まず本章ではサーバサイドの API を実装します。
基本的には Laravel にデフォルトで備えられている認証機能をそのまま使います。
API 用のルート
API と画面のルート定義は別々のファイルに記述した方が分かりやすいので、API のルート定義は前回画面を返却するルートを定義した routes/web.php
ではなく api.php
に記載しましょう。そのためには少しデフォルトの定義を変更する必要があります。
app/Providers/RouteServiceProvider.php
を以下の通りに編集してください。
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('web') // ★ 'api' → 'web' に変更
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
RouteServiceProvider
はアプリケーションの起動時にルート定義を読み込むためのクラスです。routes/api.php
に記述したルート定義に適用されるミドルウェアグループは api
なのですが、それを web
に変更します。
ちなみにミドルウェアグループの定義は app/Http/Kernel.php
に記述されています。
(こちらのファイルは参考で載せているので編集の必要はありません。)
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
web
とくらべて api
はずいぶんすっきりしていますね。api
ミドルウェアグループでは本来、外部のアプリケーションから呼び出されるようなステートレスな Web API(Twitter API や Google Map API などをイメージしてください)が想定されているので、セッションやクッキー、CSRF トークンを扱うミドルウェアが含まれていません。
今回実装する API は内部からしか呼ばれない上にクッキー認証を行うステートフルなものなので、ミドルウェアグループは画面と同じ web
に設定します。
ではここから「会員登録」「ログイン」「ログアウト」の3本の API を実装します。
といっても認証機能に関してはデフォルト実装のカスタマイズで OK です。
テストの準備
実装してすぐにプログラムの動作を確認できるように、テストコードを書きながら開発しましょう。テストコードがないとフロントエンドを実装して API を呼び出すコードを作り終わるまでプログラムの正しさが分かりませんが、それでは不便ですよね。
ここではあくまで API の動作を確かめるためにテストコードを用います。そのため失敗するテストケースや入力値バリデーションを一つずつ確かめるテストケースなどは記述しません。必要だと思う方は各自でケースを追加していただければと思います。
では、テストコード作成前にいくつか準備を行います。
インメモリの SQLite を用いる
テスト実行時に開発用の PostgreSQL データベースとは別の SQLite データベースが用いるための設定を行います。インメモリの SQLite を利用するのでテスト実行が終わると消去され無駄なデータが残りません。
まず config/database.php
の connections
に以下の接続情報を追加しましょう。
'sqlite_testing' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
そして phpunit.xml
に DB 接続の設定を追記します。
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_CONNECTION" value="sqlite_testing"/> <!-- ★ 追加 -->
<!-- 以下略 -->
会員登録 API
I/O 設計
簡単に Input(リクエストデータ)と Output(レスポンス)を設計します。
リクエスト
デフォルトの通り name
email
password
password_confirmation
を受け取ります。
レスポンス
デフォルトの挙動では登録成功後には定義されたページにリダイレクトするレスポンスを返しますが、今回は SPA なのでいつどのページに遷移するかはフロントエンドでコントロールします。
そのため、登録成功後は登録ユーザーの情報を返却させることにします。そうすれば返却されたユーザーデータをフロントエンドで保存しておいて認証状態のチェックなどに使えます。
テストコード
artisan
コマンドでテストコードを作成します。
$ php artisan make:test RegisterApiTest
雛形 tests/Feature/RegisterApiTest.php
が作成されるので以下の内容で編集してください。
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegisterApiTest extends TestCase
{
use RefreshDatabase;
/**
* @test
*/
public function should_新しいユーザーを作成して返却する()
{
$data = [
'name' => 'vuesplash user',
'email' => 'dummy@email.com',
'password' => 'test1234',
'password_confirmation' => 'test1234',
];
$response = $this->json('POST', route('register'), $data);
$user = User::first();
$this->assertEquals($data['name'], $user->name);
$response
->assertStatus(201)
->assertJson(['name' => $user->name]);
}
}
assertStatus
や assertJson
は Laravel によって追加されたアサーションです。
詳しくはマニュアル( 公式 / 日本語)を参照してください。
これ以降も、API を作成するときは事前にテストコードを書いてから実装を行います。ただしあまり詳しい説明は行いませんので、テストコードの部分が難しいと感じた方は、とりあえずテストは省略して読み進んでもアプリケーションは完成します。個人的には、テストで挫折するくらいならまずはテストの箇所は飛ばしてでも最後までクリアした方が学びになると思います。無理せず自分のレベルに合わせて判断してください。
ルート定義
routes/api.php
にルート定義を記述します。
<?php
use Illuminate\Http\Request;
// 会員登録
Route::post('/register', 'Auth\RegisterController@register')->name('register');
コントローラー
app/Http/Controllers/Auth/RegisterController.php
を以下の通り編集します。
use Illuminate\Http\Request; // ★ 追加
class RegisterController extends Controller
{
/* 中略 */
// ★ メソッド追加
protected function registered(Request $request, $user)
{
return $user;
}
}
なぜこのメソッドを追加するかを把握するために、register
メソッド が定義されている Illuminate\Foundation\Auth\RegistersUsers
トレイトを見てみましょう(RegistersUsers
トレイトは RegisterController
で追加されています)。
public function register(Request $request)
{
/* 中略 */
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
protected function registered(Request $request, $user)
{
//
}
register
メソッドの最後の return
文で registered
メソッドが呼ばれ、その戻り値が偽値だった場合には redirect
関数が呼ばれる仕組みになっています。そして registered
メソッド自体は中身が実装されていません。つまりデフォルトでは redirect
関数が呼ばれるようになっていて、このレスポンスをカスタマイズしたい場合はトレイトを使用している RegisterController
で registered
メソッドの中身を実装して上書きしてやればよいわけです。
テスト実施
ここまでできたらテストを実行して API が期待通りに動作していることを確かめます。
$ ./vendor/bin/phpunit --testdox
ログイン API
I/O 設計
リクエスト
デフォルトの通り email
password
を受け取ります。
レスポンス
会員登録同様、デフォルトの挙動では認証成功後には定義されたページにリダイレクトするレスポンスを返しますが、今回は登録ユーザーの情報を返却させることにします。
テストコード
artisan
コマンドでテストコードを作成します。
$ php artisan make:test LoginApiTest
雛形 tests/Feature/LoginApiTest.php
が作成されるので以下の内容で編集してください。
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LoginApiTest extends TestCase
{
use RefreshDatabase;
public function setUp(): void
{
parent::setUp();
// テストユーザー作成
$this->user = factory(User::class)->create();
}
/**
* @test
*/
public function should_登録済みのユーザーを認証して返却する()
{
$response = $this->json('POST', route('login'), [
'email' => $this->user->email,
'password' => 'password',
]);
$response
->assertStatus(200)
->assertJson(['name' => $this->user->name]);
$this->assertAuthenticatedAs($this->user);
}
}
ルート定義
routes/api.php
にルート定義を記述します。
// ログイン
Route::post('/login', 'Auth\LoginController@login')->name('login');
コントローラー
app/Http/Controllers/Auth/LoginController.php
を以下の通り編集します。
use Illuminate\Http\Request; // ★ 追加
class LoginController extends Controller
{
/* 中略 */
// ★ メソッド追加
protected function authenticated(Request $request, $user)
{
return $user;
}
}
authenticated
メソッドを追加する理由も会員登録のときと同じパターンです。LoginController
が使用している Illuminate\Foundation\Auth\AuthenticatesUsers
トレイトで login
メソッドの実装を見てみましょう。
public function login(Request $request)
{
/* 中略 */
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
/* 中略 */
}
protected function sendLoginResponse(Request $request)
{
/* 中略 */
return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}
protected function authenticated(Request $request, $user)
{
//
}
login
メソッドでログイン成功時に sendLoginResponse
メソッドが呼ばれ、sendLoginResponse
メソッドの return
文でレスポンスが決まっています。return
文では実装が空の authenticated
メソッドが呼ばれて、戻り値が偽であった場合にリダイレクトレスポンスが返されています。
つまり authenticated
メソッドをオーバーライドすればレスポンスをカスタマイズできますね。
テスト実施
ここまでできたらテストを実行して API が期待通りに動作していることを確かめます。
$ ./vendor/bin/phpunit --testdox
ログアウト API
I/O 設計
リクエスト
ログアウトもリクエストデータはデフォルトの通りです。
つまり何も受け取りません。
レスポンス
会員登録とログイン同様、デフォルトの挙動では認証成功後には定義されたページにリダイレクトするレスポンスを返します。今回はログアウト処理が成功したことだけが分かればよいのでステータスコード 200 番だけを返却させることにします。
テストコード
artisan
コマンドでテストコードを作成します。
$ php artisan make:test LogoutApiTest
雛形 tests/Feature/LogoutApiTest.php
が作成されるので以下の内容で編集してください。
<?php
namespace Tests\Feature;
use App\User;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
class LogoutApiTest extends TestCase
{
use RefreshDatabase;
public function setUp(): void
{
parent::setUp();
// テストユーザー作成
$this->user = factory(User::class)->create();
}
/**
* @test
*/
public function should_認証済みのユーザーをログアウトさせる()
{
$response = $this->actingAs($this->user)
->json('POST', route('logout'));
$response->assertStatus(200);
$this->assertGuest();
}
}
ルート定義
routes/api.php
にルート定義を記述します。
// ログアウト
Route::post('/logout', 'Auth\LoginController@logout')->name('logout');
コントローラー
LoginController
に loggedOut
メソッドを追加します。
protected function loggedOut(Request $request)
{
// セッションを再生成する
$request->session()->regenerate();
return response()->json();
}
loggedOut
メソッドを追加する理由も会員登録・ログインと同じパターンです。ログインと同様に AuthenticatesUsers
トレイトの logout
メソッドを見てみましょう。
public function logout(Request $request)
{
/* 中略 */
return $this->loggedOut($request) ?: redirect('/');
}
protected function loggedOut(Request $request)
{
//
}
同じパターンの繰り返しなのでもう説明は不要ですね。
loggedOut
メソッドをオーバーライドすることでレスポンスをカスタマイズできます。
Laravel では認証機能が最初から備わっている上で、デフォルト実装のカスタマイズもしやすいように作られています。
テスト実施
ここまでできたらテストを実行して API が期待通りに動作していることを確かめます。
$ ./vendor/bin/phpunit --testdox
認証機能に必要な3本の API が完成しました。
今回はテストコードを使用して API の動作確認を行いましたが、Postman などのソフトウェアを使っても確認できます。興味がある方はチェックしてみてください。
この章はこれでおしまいです。
本章までのソースコードはリポジトリの ch-4 ブランチに置いてあります。
次の章ではログイン画面のユーザーインターフェースを作成します。
関連記事
連載記事(全16回)
Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう
- (1) イントロダクション
- (2) アプリケーションの設計
- (3) SPA開発環境とVue Router
- (4) 認証API
- (5) 認証ページ
- (6) 認証機能とVuex
- (7) 認証機能とVuex Part.2
- (8) エラーハンドリング
- (9) 写真投稿API
- (10) 写真投稿フォーム
- (11) 写真一覧取得API
- (12) 写真一覧ページ
- (13) 写真詳細ページ
- (14) コメント投稿機能
- (15) いいね機能
- (16) エラーハンドリング Part.2