はじめに
この記事は、3本立ての2本目です。
- Laravel と Vue と Docker でシンプルな SPA を作る ①【フロントエンド】
- Laravel と Vue と Docker でシンプルな SPA を作る ②【バックエンド】←今ここ
- Laravel と Vue と Docker でシンプルな SPA を作る ③【フロントエンドとバックエンドの通信】
今回は、下記2つのコミットの内容です。後でrebaseしておきます。
マイグレーションファイルの作成と適用
ドキュメントに従って、Tasks
テーブルを作成するためのマイグレーションファイルを作成します。なお、DBの文字コードは/src/config/database.php での設定(この場合UTF-8)が反映されています。
$ docker-compose exec app php artisan make:migration create_tasks_table
作成されたマイグレーションファイルsrc/database/migrations/2023_03_02_231934_create_tasks_table.php
に、テーブルを追加します。ちなみに、マイグレーションファイルの日付の部分は、作成した日時が入ります。テーブルで指定できるカラムのタイプは、ここから選べます。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->bigIncrements('id');
+ $table->string('title', 100);
+ $table->string('content', 100);
+ $table->string('person_in_charge', 100);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
作成したマイグレーションを流します。
$ docker-compose exec app php artisan migrate
これでTasks
テーブルが作成されました。
モデルの作成
LaravelのORMであるEloquentでDBとやり取りをするためのモデルを作成します。
ドキュメントに従って、モデルを作成します。
$ docker-compose exec app php artisan make:model Task
作成されたsrc/app/Models/Task.php
に、$fillableだけを定義しておきます。 理由の詳細は後述しますが、インサート処理に必要となるためです。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $fillable = [
+ 'title',
+ 'content',
+ 'person_in_charge',
];
}
テストデータを作成する
後にDBとのデータ通信の挙動を確認するためにも、Seedを使ってダミーのテストデータを10件作っておきます。
$ docker-compose exec app php artisan make:seeder TaskSeeder
作成されたsrc/database/seeders/TaskSeeder.php
の中でfor文を回してダミーデータを作ってもいいのですが、せっかくなのでEloquentの機能である、Factoryを使ってみます。
$ docker-compose exec app php artisan make:factory TaskFactory
生成されたsrc/database/factories/TaskFactory.php
に、Factoryが使っているライブラリであるFakerPHPのフォーマットに従って、ダミーデータを作成するメソッドと、出力先のカラムを指定します。
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Task>
*/
class TaskFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
+ 'title' => fake()->word(),
+ 'content' => fake()->sentence(),
+ 'person_in_charge' => fake()->name(),
];
}
}
このFactoryを、src/database/seeders/TaskSeeder.php
で読み込みます。
<?php
namespace Database\Seeders;
use App\Models\Task;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class TaskSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
+ Task::factory()->count(10)->create();
}
}
Factoryの定義を元にデータを作成します。countメソッドで指定した数のモデルを作成し、createでデータを永続化(DBに格納)させます。
シーディングの準備ができたので、流してみます。
$ docker-compose exec app php artisan db:seed
DBを見てみると、しっかり10件ダミーデータが作成されています。
APIの実装
タスク作成API
ドキュメントに従って、コントローラーを作成します。
$ docker-compose exec app php artisan make:controller TaskController
タスク作成APIはその名の通りタスクを作成するためのAPIです。HTTP POSTリクエストを受け取ると、リクエストのbody内のJSONデータをDBに格納します。タスク作成APIのURLは http://localhost:80/api/tasks です。リクエストのbody内のJSONは、下記のようなイメージです。
{
"title": "APIから登録",
"content": "テストです。",
"person_in_charge": "APIユーザー"
}
APIを作るためコントローラーを修正します。先ほどのコマンドで生成されたsrc/app/Http/Controllers/TaskController.phpに作成処理を行うstore()メソッドを定義します。本来は、トランザクションを張ったりしてエラーハンドリングをしないといけないのですが、今回は省略しています。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
class TaskController extends Controller
{
public function store(Request $request)
{
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
}
受け取ったPOSTリクエストのデータはIlluminate\Http\Requestクラスのインスタンスに格納されます。Requestインスタンスのallメソッドですべてのデータを取得し、これをTaskモデルのcreateメソッドを介してDBにデータを作成します。
ちなみに、このcreateメソッドを使う方法は、ドキュメントにも記載がある通り、マスアサインメントから保護されています。fillableかguardedをモデルで定義しないとcreateメソッドが実行できないためです。
作成されたメソッドとルーティングの紐づけを、src/routes/api.php
で行います。
+ use App\Http\Controllers\TaskController;
(省略)
+ Route::post('/tasks', [TaskController::class, 'store']);
Postmanでリクエストを投げてみると、
想定通りのレスポンスを取得できました。
デバッグして少し中身を追ってみます。$request->all()
の中身をddメソッドで見てみます。
public function store(Request $request)
{
+ dd($request->all());
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
結果は、
array:3 [ // app/Http/Controllers/TaskController.php:30
"title" => "APIから登録"
"content" => "テストです。"
"person_in_charge" => "APIユーザー"
]
ドキュメントに記載されている通り、リクエストのbodyにJSONで渡した値全てが配列で格納されていました。
次に、生成されるSQLを見てみます。下記の記事が参考になりました。
Illuminate\Support\Facades\DB
クラスのenableQueryLog()
とgetQueryLog()
を使ってデバッグします。
+ use Illuminate\Support\Facades\DB;
(省略)
public function update(Request $request, $id)
{
+ DB::enableQueryLog();
Task::create($request->all());
+ dd(DB::getQueryLog());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
結果は、
array:1 [ // app/Http/Controllers/TaskController.php:32
0 => array:3 [
"query" => "insert into "tasks" ("title", "content", "person_in_charge", "updated_at", "created_at") values (?, ?, ?, ?, ?) returning "id""
"bindings" => array:5 [
0 => "APIから登録"
1 => "テストです。"
2 => "APIユーザー"
3 => "2023-03-13 23:43:21"
4 => "2023-03-13 23:43:21"
]
"time" => 26.83
]
]
しっかり想定通りのINSERT文が生成されていました。
SQL文中のreturning "id"
ってなんだ?と思いましたが、PoestgreSQLの RETURNING句 でした。MySQLしか使ったことがなかったので、初見でした。
INSERT、UPDATE、DELETE 文で変更された内容を返す機能です。下記の記事が、使いどころをわかりやすく解説してくれていました。
タスク全件取得API
タスク全件取得APIは、その名の通りタスクを全件取得するためのAPIです。HTTP GETリクエストを受け取ると、DBにあるレコード全件を取得して返します。タスク作成APIのURLは http://localhost:80/api/tasks です。
APIを作るためコントローラーを修正します。src/app/Http/Controllers/TaskController.phpに全件取得処理を行うindex()メソッドを定義します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
class TaskController extends Controller
{
+ public function index()
+ {
+ return Task::all();
+ }
public function store(Request $request)
{
dd($request->all());
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
}
src/routes/api.php
にルーティングを記載します。
(省略)
+ Route::get('/tasks', [TaskController::class, 'index']);
Route::post('/tasks', [TaskController::class, 'store']);
リクエストを投げると、
しっかり全権がJSONで返ってきました。
クエリを確認すると、
public function index()
{
+ DB::enableQueryLog();
- return Task::all();
+ Task::all();
+ dd(DB::getQueryLog());
}
結果は、
array:1 [ // app/Http/Controllers/TaskController.php:15
0 => array:3 [
"query" => "select * from "tasks""
"bindings" => []
"time" => 26.68
]
]
想定通りtasks
テーブルから全件取得するSQLが生成されていました。
タスク一件取得API
タスク一件取得APIは、指定したタスクを一件取得するためのAPIです。HTTP GETリクエストを受け取ると、URL上のidと一致するPKのレコードをDBから取得して返します。タスク作成APIのURLは http://localhost:80/api/tasks/{id} です。
APIを作るためコントローラーを修正します。src/app/Http/Controllers/TaskController.phpに指定タスク一件取得処理を行うshow()メソッドを定義します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
class TaskController extends Controller
{
public function index()
{
return Task::all();
}
+ public function show($id)
+ {
+ $task = Task::find($id);
+ if ($task) {
+ return $task;
+ } else {
+ return response()->json([
+ "message" => "Task not found",
+ ], Response::HTTP_NOT_FOUND);
+ }
+ }
public function store(Request $request)
{
dd($request->all());
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
}
src/routes/api.php
にルーティングを記載します。
Route::get('/tasks', [TaskController::class, 'index']);
+ Route::get('/tasks/{id}', [TaskController::class, 'show']);
Route::post('/tasks', [TaskController::class, 'store']);
リクエストを投げてみます。まずは、正常系です。
問題なしです。次に、異常系です。パラメータで指定したidのタスクが存在しないパターンです。
問題なかったです。次に、SQLの中身を確認してみます。
public function show($id)
{
+ DB::enableQueryLog();
$task = Task::find($id);
+ dd(DB::getQueryLog());
if ($task) {
return $task;
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
結果は、
array:1 [ // app/Http/Controllers/TaskController.php:19
0 => array:3 [
"query" => "select * from "tasks" where "tasks"."id" = ? limit 1"
"bindings" => array:1 [
0 => "6"
]
"time" => 36.89
]
]
想定通りのSELECT文が生成されていました。
タスク更新API
タスク更新APIは、指定したタスクを更新するためのAPIです。HTTP PUTリクエストを受け取ると、URL上のidと一致するPKのレコードをリクエストのbodyのJSONの内容で更新します。タスク作成APIのURLは http://localhost:80/api/tasks/{id} です。
APIを作るためコントローラーを修正します。src/app/Http/Controllers/TaskController.phpに更新処理を行うupdate()メソッドを定義します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
class TaskController extends Controller
{
public function index()
{
return Task::all();
}
public function show($id)
{
$task = Task::find($id);
if ($task) {
return $task;
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
public function store(Request $request)
{
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
+ public function update(Request $request, $id)
+ {
+ $task = Task::find($id);
+ if ($task) {
+ $task->title = is_null($request->title) ? $task->title : $request->title;
+ $task->content = is_null($request->content) ? $task->content : $request->content;
+ $task->person_in_charge = is_null($request->person_in_charge) ? $task->person_in_charge : $request->person_in_charge;
+ $task->save();
+ return response()->json([
+ "message" => "updated successfully",
+ ], Response::HTTP_OK);
+ } else {
+ return response()->json([
+ "message" => "Task not found",
+ ], Response::HTTP_NOT_FOUND);
+ }
+ }
}
Request
とTask
モデルを受け取って、Request
の内容でTask
モデルに対してUPDATEをかけています。
ルーティングを追加します。
Route::get('/tasks', [TaskController::class, 'index']);
Route::get('/tasks/{task}', [TaskController::class, 'show']);
Route::post('/tasks', [TaskController::class, 'store']);
+ Route::put('/tasks/{task}', [TaskController::class, 'update']);
正常系のリクエストを投げます。
問題ありません。次に、異常系です。パラメータで指定したidのタスクが存在しないパターンです。
これも大丈夫そうです。次に、SQLを見てみます。
public function update(Request $request, $id)
{
$task = Task::find($id);
if ($task) {
+ DB::enableQueryLog();
$task->title = is_null($request->title) ? $task->title : $request->title;
$task->content = is_null($request->content) ? $task->content : $request->content;
$task->person_in_charge = is_null($request->person_in_charge) ? $task->person_in_charge : $request->person_in_charge;
$task->save();
+ dd(DB::getQueryLog());
return response()->json([
"message" => "updated successfully",
], Response::HTTP_OK);
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
結果は、
array:2 [ // app/Http/Controllers/TaskController.php:38
0 => array:3 [
"query" => "select * from "tasks" where "tasks"."id" = ? limit 1"
"bindings" => array:1 [
0 => "5"
]
"time" => 10.07
]
1 => array:3 [
"query" => "update "tasks" set "title" = ?, "content" = ?, "person_in_charge" = ?, "updated_at" = ? where "id" = ?"
"bindings" => array:5 [
0 => "更新APIで更新"
1 => "更新しました。"
2 => "APIテストユーザー。"
3 => "2023-03-13 12:42:57"
4 => 5
]
"time" => 2.32
]
]
想定通りのSELECT文とUPDATE文が実行されていました。
タスク削除API
タスク削除APIは、指定したタスクを削除するためのAPIです。HTTP DELETEリクエストを受け取ると、URL上のidと一致するPKのレコードを削除します。タスク作成APIのURLは http://localhost:80/api/tasks/{id} です。
APIを作るためコントローラーを修正します。src/app/Http/Controllers/TaskController.phpに削除処理を行うdestroy()メソッドを定義します。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Task;
class TaskController extends Controller
{
public function index()
{
return Task::all();
}
public function show($id)
{
$task = Task::find($id);
if ($task) {
return $task;
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
public function store(Request $request)
{
Task::create($request->all());
return response()->json([
"message" => "created successfully",
], Response::HTTP_CREATED);
}
public function update(Request $request, $id)
{
$task = Task::find($id);
if ($task) {
$task->title = is_null($request->title) ? $task->title : $request->title;
$task->content = is_null($request->content) ? $task->content : $request->content;
$task->person_in_charge = is_null($request->person_in_charge) ? $task->person_in_charge : $request->person_in_charge;
$task->save();
return response()->json([
"message" => "updated successfully",
], Response::HTTP_OK);
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
+ public function destroy(Task $task)
+ {
+ $task = Task::find($id);
+ if ($task) {
+ $task->delete();
+ return response()->json([
+ "message" => "deleted successfully",
+ ], Response::HTTP_OK);
+ } else {
+ return response()->json([
+ "message" => "Task not found",
+ ], Response::HTTP_NOT_FOUND);
+ }
}
ルーティングを追加します。
Route::get('/tasks', [TaskController::class, 'index']);
Route::get('/tasks/{task}', [TaskController::class, 'show']);
Route::post('/tasks', [TaskController::class, 'store']);
Route::put('/tasks/{task}', [TaskController::class, 'update']);
+ Route::delete('/tasks/{task}', [TaskController::class, 'destroy']);
正常系のリクエストを投げてみると、
次に異常系ですが、
問題ありません。クエリを見てみます。
public function destroy($id)
{
+ DB::enableQueryLog();
$task = Task::find($id);
if ($task) {
$task->delete();
+ dd(DB::getQueryLog());
return response()->json([
"message" => "deleted successfully",
], Response::HTTP_OK);
} else {
return response()->json([
"message" => "Task not found",
], Response::HTTP_NOT_FOUND);
}
}
結果は、
array:2 [ // app/Http/Controllers/TaskController.php:46
0 => array:3 [
"query" => "select * from "tasks" where "tasks"."id" = ? limit 1"
"bindings" => array:1 [
0 => "5"
]
"time" => 31.23
]
1 => array:3 [
"query" => "delete from "tasks" where "id" = ?"
"bindings" => array:1 [
0 => 5
]
"time" => 2.37
]
]
想定通りのSELECT文とDELETE文が生成されていました。
もちろんエラーハンドリングなどやるべきことはあるのですが、最低限これでAPIの実装が完了しました。
終わりに
次回は、今回作ったAPIを、前回作ったVueのフロントから呼び出して画面に表示する処理を実装します。Laravelのリクエスト受信やDBとのやり取りの大きな流れはわかったので、もう少し内部的な仕組みをドキュメントやソースコードでこれから深ぼっていきたいと思います。