Zenn
🔄

【Laravel】TODOリストAPI実装(基本機能)

2025/03/23に公開
1

はじめに

前回の記事では、Laravel の開発環境構築とプロジェクトの作成を行いました。

https://zenn.dev/muranaka/articles/f801e633e6abaa

今回は、その続きとして、Flutter アプリで利用する TODO リスト管理 API の基本機能を実装していきます。

TODO リスト API の機能要件

実装する API には以下の機能を持たせます:

  1. TODO の一覧取得 (GET)
  2. TODO の詳細取得 (GET)
  3. TODO の新規作成 (POST)
  4. TODO の更新 (PUT)
  5. TODO の削除 (DELETE)
  6. 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)。

作成されたマイグレーションファイルを編集します:

database/migrations/yyyy_mm_dd_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ファイルを作成します。作成されたファイルを編集して、必要なプロパティを設定しましょう。

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ファイルを編集します。

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ファイルを編集します。

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 として利用する場合には個別にインストールする必要がありました。

https://zenn.dev/pcs_engineer/articles/laravel11-faq#routes%2Fapi.phpがない

そのため、以下を実行します。

php artisan install:api

ファイル生成が確認できたら API ルートを設定して、コントローラーのメソッドと URL をマッピングします。

routes/api.php
<?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)

リクエスト例

{
  "title": "Laravelの勉強",
  "description": "APIの作成方法を学ぶ",
  "priority": 2,
  "due_date": "2025-04-15"
}

Content-Type を指定します。

Body で値を設定します。

レスポンス例:

作成された TODO 項目 をレスポンスする仕様になっています。

2. TODO の一覧取得(GET /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})

4. TODO の更新(PUT /api/v1/todos/{id})

{
  "title": "Laravelの勉強を完了する",
  "description": "APIの作成方法をマスターする",
  "priority": 3,
  "due_date": "2025-04-01"
}

5. TODO の完了状態切り替え(PATCH /api/v1/todos/{id}/toggle-complete)

6. TODO の削除(DELETE /api/v1/todos/{id})

次回はこちらの API を用いて、Flutter アプリを作成していきます。

1

Discussion

ログインするとコメントできます