【Laravel】TODOリストAPI実装(基本機能)
はじめに
前回の記事では、Laravel の開発環境構築とプロジェクトの作成を行いました。
今回は、その続きとして、Flutter アプリで利用する TODO リスト管理 API の基本機能を実装していきます。
TODO リスト API の機能要件
実装する API には以下の機能を持たせます:
- TODO の一覧取得 (GET)
- TODO の詳細取得 (GET)
- TODO の新規作成 (POST)
- TODO の更新 (PUT)
- TODO の削除 (DELETE)
- TODO の完了状態切り替え (PATCH)
実装手順
1. データベースマイグレーションの作成
まず、TODO を保存するためのテーブルを作成します。マイグレーションファイルを作成しましょう。
php artisan make:migration create_todos_table
以下の出力であれば OK です。
INFO Migration [database/migrations/2025_03_12_014644_create_todos_table.php] created successfully.
このコマンドを実行すると、database/migrations
ディレクトリに新しいマイグレーションファイルが作成されます。ファイル名は日付とタイムスタンプが付いた形式になっています(例:2025_03_22_000000_create_todos_table.php
)。
作成されたマイグレーションファイルを編集します:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* TODOテーブルのカラムを定義します
* id: 自動採番のID
* title: TODOのタイトル
* description: TODOの詳細説明
* priority: 優先度(低:1、中:2、高:3)
* due_date: 期限日
* completed: 完了フラグ
* timestamps: 作成日時と更新日時
*/
public function up(): void
{
Schema::create('todos', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->integer('priority')->default(2); // 1:低、2:中、3:高
$table->date('due_date')->nullable();
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
/**
* テーブルを削除するためのメソッド
*/
public function down(): void
{
Schema::dropIfExists('todos');
}
};
これで、TODO タスクを保存するためのテーブル構造を定義しました。次に、マイグレーションを実行して、実際にデータベースにテーブルを作成します。
php artisan migrate
以下のような出力が表示されれば OK です。
INFO Running migrations.
2025_03_12_014644_create_todos_table ........... 14.97ms DONE
2. モデルの作成
次に、データベースとのやり取りを管理するモデルを作成します。
php artisan make:model Todo
以下のような出力が表示されれば OK です。
INFO Model [app/Models/Todo.php] created successfully.
このコマンドは、app/Models/Todo.php
ファイルを作成します。作成されたファイルを編集して、必要なプロパティを設定しましょう。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Todoモデルの定義
* Todoテーブルとのやり取りを管理するモデルクラスです
*/
class Todo extends Model
{
use HasFactory;
/**
* 一括代入可能なカラムを指定
*/
protected $fillable = [
'title',
'description',
'priority',
'due_date',
'completed'
];
/**
* 属性のデータ型をキャスト
*/
protected $casts = [
'completed' => 'boolean',
'due_date' => 'date',
'priority' => 'integer',
];
/**
* 優先度のラベルを取得
*/
public function priorityLabel()
{
return match($this->priority) {
1 => '低',
2 => '中',
3 => '高',
default => '未設定',
};
}
/**
* 期限切れかどうかを判定
*/
public function isOverdue()
{
if (!$this->due_date || $this->completed) {
return false;
}
return $this->due_date->isPast();
}
}
3. API リソースの作成
API 経由でデータを返す際のフォーマットを定義するリソースクラスを作成します。これにより、一貫したレスポンス形式を提供することができます。
php artisan make:resource TodoResource
以下のような出力が表示されれば OK です。
INFO Resource [app/Http/Resources/TodoResource.php] created successfully.
作成されたapp/Http/Resources/TodoResource.php
ファイルを編集します。
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* TodoリソースクラスはAPIレスポンスの形式を定義します
*/
class TodoResource extends JsonResource
{
/**
* リソースを配列に変換
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'description' => $this->description,
'priority' => [
'value' => $this->priority,
'label' => $this->priorityLabel(),
],
'due_date' => $this->due_date ? $this->due_date->format('Y-m-d') : null,
'completed' => $this->completed,
'is_overdue' => $this->isOverdue(),
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
];
}
}
4. API 用コントローラーの作成
次に、API リクエストを処理するコントローラーを作成します。--api
フラグを使用することで、API リソースコントローラー用のメソッドが自動生成されます。
php artisan make:controller Api/TodoController --api
以下のような出力が表示されれば OK です。
INFO Controller [app/Http/Controllers/Api/TodoController.php] created successfully.
作成されたapp/Http/Controllers/Api/TodoController.php
ファイルを編集します。
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\TodoResource;
use App\Models\Todo;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\Response;
/**
* Todo APIコントローラー
* TODOリストのREST API操作を管理するコントローラー
*/
class TodoController extends Controller
{
/**
* TODOの一覧を取得
*
* @return AnonymousResourceCollection
*/
public function index(Request $request)
{
// クエリパラメータによるフィルタリング
$query = Todo::query();
// 完了状態でフィルタリング
if ($request->has('completed')) {
$query->where('completed', $request->boolean('completed'));
}
// 優先度でフィルタリング
if ($request->has('priority')) {
$query->where('priority', $request->priority);
}
// 並び順の設定
$sortField = $request->input('sort_by', 'created_at');
$sortDirection = $request->input('sort_direction', 'desc');
// 有効なソートフィールドのみ許可
$allowedSortFields = ['title', 'priority', 'due_date', 'created_at', 'updated_at'];
if (in_array($sortField, $allowedSortFields)) {
$query->orderBy($sortField, $sortDirection);
}
// データ取得と変換
$todos = $query->get();
return TodoResource::collection($todos);
}
/**
* 新しいTODOを作成
*
* @param Request $request
* @return TodoResource
*/
public function store(Request $request)
{
// バリデーション
$validated = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'priority' => 'nullable|integer|min:1|max:3',
'due_date' => 'nullable|date',
'completed' => 'nullable|boolean',
]);
// TODOの作成
$todo = Todo::create($validated);
return new TodoResource($todo);
}
/**
* 指定されたTODOの詳細を取得
*
* @param Todo $todo
* @return TodoResource
*/
public function show(Todo $todo)
{
return new TodoResource($todo);
}
/**
* 指定されたTODOを更新
*
* @param Request $request
* @param Todo $todo
* @return TodoResource
*/
public function update(Request $request, Todo $todo)
{
// バリデーション
$validated = $request->validate([
'title' => 'sometimes|required|string|max:255',
'description' => 'nullable|string',
'priority' => 'sometimes|nullable|integer|min:1|max:3',
'due_date' => 'nullable|date',
'completed' => 'sometimes|boolean',
]);
// TODOの更新
$todo->update($validated);
return new TodoResource($todo);
}
/**
* 指定されたTODOを削除
*
* @param Todo $todo
* @return Response
*/
public function destroy(Todo $todo)
{
$todo->delete();
return response()->json(['message' => 'Todo deleted successfully'], 200);
}
/**
* 完了状態を切り替える
*
* @param Todo $todo
* @return TodoResource
*/
public function toggleComplete(Todo $todo)
{
$todo->completed = !$todo->completed;
$todo->save();
return new TodoResource($todo);
}
}
5. API ルートの設定
最後に、routes/api.php
ファイルを編集します。
ところが、、、
routes/api.php が存在しません。調査していると、
routes/api.php がない
API 関連の機能は必要な人だけ追加するように変わったので個別にインストールする。
という記載があり、API として利用する場合には個別にインストールする必要がありました。
そのため、以下を実行します。
php artisan install:api
ファイル生成が確認できたら API ルートを設定して、コントローラーのメソッドと URL をマッピングします。
<?php
use App\Http\Controllers\Api\TodoController;
use Illuminate\Support\Facades\Route;
/**
* API用のルート設定
* プレフィックスはすでに「api」になっているため、URLは「/api/todos」のようになります
*/
// APIバージョン管理
Route::prefix('v1')->group(function () {
// TODOリソースのCRUD操作
Route::apiResource('todos', TodoController::class);
// 完了状態を切り替えるカスタムルート
Route::patch('todos/{todo}/toggle-complete', [TodoController::class, 'toggleComplete']);
});
これで基本的な API の実装は完了です。次に、API をテストしてみましょう。
API のテスト
まずは、api が登録されているかどうかを確認する必要があります。
php artisan route:list --path=api
以下の出力のように、追加した API が表示されていれば OK です。
GET|HEAD / .....
GET|HEAD api/user ......
GET|HEAD api/v1/todos ..... todos.index › Api\TodoController@index
POST api/v1/todos ..... todos.store › Api\TodoController@store
GET|HEAD api/v1/todos/{todo} ... todos.show › Api\TodoController@show
PUT|PATCH api/v1/todos/{todo} ... todos.update › Api\TodoController@update
DELETE api/v1/todos/{todo} ... todos.destroy › Api\TodoController@destroy
PATCH api/v1/todos/{todo}/toggle-complete .... Api\TodoController@toggleComplete
GET|HEAD sanctum/csrf-cookie sanctum.csrf-cookie › Laravel\Sanctum › CsrfCookieController@show
GET|HEAD storage/{path} ..... storage.local
GET|HEAD up ...............................
Postman を使ったテスト
API をテストするには、Postmanなどの API クライアントツールを使用すると便利です。以下は、各エンドポイントのテスト方法です。
1. TODO の作成(POST /api/v1/todos)
リクエスト例
- メソッド: POST
- URL: http://localhost:8000/api/v1/todos
- Headers:
- Content-Type: application/json
- Body (raw JSON):
{
"title": "Laravelの勉強",
"description": "APIの作成方法を学ぶ",
"priority": 2,
"due_date": "2025-04-15"
}
Content-Type を指定します。
Body で値を設定します。
レスポンス例:
作成された TODO 項目 をレスポンスする仕様になっています。
2. TODO の一覧取得(GET /api/v1/todos)
- メソッド: GET
- URL: http://localhost:8000/api/v1/todos
クエリパラメータを使用してフィルタリングや並べ替えも可能です:
- 完了済みの TODO のみ:
/api/v1/todos?completed=true
- 優先度「高」の TODO のみ:
/api/v1/todos?priority=3
- タイトルで昇順ソート:
/api/v1/todos?sort_by=title&sort_direction=asc
レスポンス例
{
"data": [
{
"id": 3,
"title": "Laravelの勉強",
"description": "APIの作成方法を学ぶ",
"priority": {
"value": 2,
"label": "中"
},
"due_date": "2025-04-15",
"completed": false,
"is_overdue": false,
"created_at": "2025-03-12 03:01:52",
"updated_at": "2025-03-12 03:01:52"
},
// 省略
]
}
3. TODO の詳細取得(GET /api/v1/todos/{id})
- メソッド: GET
- URL: http://localhost:8000/api/v1/todos/1
4. TODO の更新(PUT /api/v1/todos/{id})
- メソッド: PUT
- URL: http://localhost:8000/api/v1/todos/1
- Headers:
- Content-Type: application/json
- Body (raw JSON):
{
"title": "Laravelの勉強を完了する",
"description": "APIの作成方法をマスターする",
"priority": 3,
"due_date": "2025-04-01"
}
5. TODO の完了状態切り替え(PATCH /api/v1/todos/{id}/toggle-complete)
- メソッド: PATCH
- URL: http://localhost:8000/api/v1/todos/1/toggle-complete
6. TODO の削除(DELETE /api/v1/todos/{id})
- メソッド: DELETE
- URL: http://localhost:8000/api/v1/todos/1
次回はこちらの API を用いて、Flutter アプリを作成していきます。
Discussion