mcpField
L
laravel-api

laravel-api

github.com/juststeveking開発31288 回インストール41 週間前に更新
リポジトリ

laravel-api について

laravel-apiは、Laravel REST APIを構築する開発スキルです。新規APIの実装や既存コードのレビューに取り組む開発者向けに、ステートレス設計、リソース単位の構成、明確なレイヤー分けと、実装指針やテンプレートを提供します。

主な機能

  1. 01リソース単位でルートとコントローラーを構成
  2. 02ステートレスな多層アーキテクチャの原則
  3. 03CRUDエンドポイント構築の手順
  4. 04APIバージョン管理とレスポンス形式の標準化
  5. 05コンポーネントテンプレートとコードレビュー指針

活用シーン

  1. 01LaravelプロジェクトでREST APIを構築
  2. 02リソースにバージョン管理されたCRUDを追加
  3. 03APIコードの一貫性を保つリファクタリングやレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/juststeveking/laravel-api-skill --skill laravel-api

インストール後、Claude Code で/laravel-apiを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/templates/
Action.php
Controller.php
FormRequest.php
Model.php
Payload.php
references/
architecture.md
code-examples.md
code-quality.md

詳細

Laravel API - Steve's Architecture

クリーンでステートレス、リソース単位のスコープを持つアーキテクチャで Laravel REST API を構築します。

クイックスタート

ユーザーが Laravel API をリクエストした場合、次のワークフローに従います。

  1. 要件を理解する - どのようなリソースが必要ですか?どのような操作ですか?認証は必要ですか?
  2. プロジェクト構造を初期化する - ルーティングを設定し、フロントエンドの不要な部分を削除します
  3. 最初のリソースを構築する - パターンを確立するために完全な CRUD を実装します
  4. 認証を追加する - PHP Open Source Saver による JWT
  5. 残りのリソースを反復開発する - 確立したパターンに従います

コアアーキテクチャの原則

詳細については references/architecture.md をお読みください。主要な原則は次のとおりです。

  1. 設計によるステートレス - 隠れた依存関係がなく、明示的なデータフロー
  2. 境界優先 - HTTP、ビジネスロジック、データ層の明確な分離
  3. リソース単位のスコープ - ルートとコントローラーをリソースごとに整理
  4. バージョン規律 - 名前空間ベースのバージョニング、HTTP Sunset ヘッダー

コード品質基準

すべてのコードは Laravel のベストプラクティスと PSR-12 標準に従う必要があります。

  1. 機能を保持する - リファクタリングはコードの「動作の仕方」を変えるものであり、「動作そのもの」を変えてはなりません
  2. 暗黙より明示 - 巧みなショートカットよりも、明確で読みやすいコードを優先します
  3. 型宣言 - メソッドには必ず戻り値の型を、パラメータには有益な場合に型を付けます
  4. ネストした三項演算子を避ける - 明確さのために match 式、switch、または if/else を使用します
  5. 一貫した命名 - PSR-12 と Laravel の規約に厳密に従います
  6. 適切な名前空間 - インポートを論理的に整理し、完全な型ヒントを使用します

コードをレビューまたはリファクタリングする場合:

  • 巧みさよりも明確さと保守性に焦点を当てます
  • 複雑なネストしたロジックを読みやすい構造に単純化します
  • マジック値を名前付き定数または設定に抽出します
  • 正確な動作を保ちながら不要な複雑さを削除します

プロジェクト構造

routes/api/
  routes.php              # Main entry point, version grouping
  tasks.php               # All task routes, all versions
  projects.php            # All project routes, all versions

app/Http/
  Controllers/{Resource}/V1/
    StoreController.php   # Always invokable
    IndexController.php
    ShowController.php
  Requests/{Resource}/V1/
    StoreTaskRequest.php  # Validation + payload() method
  Payloads/{Resource}/
    StoreTaskPayload.php  # Simple DTOs with toArray()
  Responses/
    JsonDataResponse.php  # Implements Responsable
    JsonErrorResponse.php
  Middleware/
    HttpSunset.php

app/Actions/{Resource}/
  CreateTask.php          # Single-purpose business logic

app/Services/             # Only when logic too complex for Actions

app/Models/
  Task.php                # HasUlids trait, simple data access

新しいリソースエンドポイントの構築

ステップ 1: モデル

常に ULID を使用します。モデルはシンプルに保ち、データアクセスのみとします。

<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

final class Task extends Model
{
    use HasFactory;
    use HasUlids;
    
    protected $fillable = [
        'title',
        'description',
        'status',
        'project_id',
    ];

    protected $casts = [
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
    ];

    public function project(): BelongsTo
    {
        return $this->belongsTo(Project::class);
    }
}
ステップ 2: ルート

routes/api/{resource}.php にリソースルートファイルを作成します。

use App\Http\Controllers\Tasks\V1;

Route::middleware(['auth:api'])->group(function () {
    Route::get('/tasks', V1\IndexController::class);
    Route::post('/tasks', V1\StoreController::class);
    Route::get('/tasks/{task}', V1\ShowController::class);
    Route::patch('/tasks/{task}', V1\UpdateController::class);
    Route::delete('/tasks/{task}', V1\DestroyController::class);
});

routes/api/routes.php に含めます。

Route::prefix('v1')->group(function () {
    require __DIR__ . '/tasks.php';
});
ステップ 3: DTO(ペイロード)

app/Http/Payloads/{Resource}/{Operation}Payload.php に作成します。

<?php

declare(strict_types=1);

namespace App\Http\Payloads\Tasks;

final readonly class StoreTaskPayload
{
    public function __construct(
        public string $title,
        public ?string $description,
        public string $status,
        public string $projectId,
    ) {}

    public function toArray(): array
    {
        return [
            'title' => $this->title,
            'description' => $this->description,
            'status' => $this->status,
            'project_id' => $this->projectId,
        ];
    }
}
ステップ 4: フォームリクエスト

app/Http/Requests/{Resource}/V1/{Operation}Request.php に作成します。

<?php

declare(strict_types=1);

namespace App\Http\Requests\Tasks\V1;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

final class StoreTaskRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:1000'],
            'status' => ['required', Rule::in(['pending', 'in_progress', 'completed'])],
            'project_id' => ['required', 'string', 'exists:projects,id'],
        ];
    }

    public function payload(): StoreTaskPayload
    {
        return new StoreTaskPayload(
            title: $this->string('title')->toString(),
            description: $this->string('description')->toString(),
            status: $this->string('status')->toString(),
            projectId: $this->string('project_id')->toString(),
        );
    }
}
ステップ 5: アクション

app/Actions/{Resource}/{Operation}.php に作成します。

<?php

declare(strict_types=1);

namespace App\Actions\Tasks;

use App\Http\Payloads\Tasks\StoreTaskPayload;
use App\Models\Task;

final readonly class CreateTask
{
    public function handle(StoreTaskPayload $payload): Task
    {
        return Task::create($payload->toArray());
    }
}
ステップ 6: コントローラー

app/Http/Controllers/{Resource}/V1/{Operation}Controller.php に単一アクションのコントローラーを作成します。

<?php

declare(strict_types=1);

namespace App\Http\Controllers\Tasks\V1;

use App\Actions\Tasks\CreateTask;
use App\Http\Requests\Tasks\V1\StoreTaskRequest;
use App\Http\Responses\JsonDataResponse;
use Illuminate\Http\JsonResponse;

final readonly class StoreController
{
    public function __construct(
        private CreateTask $createTask,
    ) {}

    public function __invoke(StoreTaskRequest $request): JsonResponse
    {
        $task = $this->createTask->handle(
            payload: $request->payload(),
        );

        return new JsonDataResponse(
            data: $task,
            status: 201,
        );
    }
}

レスポンス形式

すべてのレスポンスの標準形式は次のとおりです。

成功:

{
    "data": {...},
    "meta": {...}
}

エラー(Problem+JSON):

{
    "type": "about:blank",
    "title": "Validation Failed",
    "status": 422,
    "detail": "The given data was invalid",
    "errors": {...}
}

クエリの構築

フィルタリング、ソート、インクルードには Spatie Query Builder を使用します。

use Spatie\QueryBuilder\QueryBuilder;

$tasks = QueryBuilder::for(Task::class)
    ->allowedFilters(['status', 'priority'])
    ->allowedSorts(['created_at', 'due_date'])
    ->allowedIncludes(['project', 'assignee'])
    ->paginate();

エンドポイントのバージョニング

V2 を作成する場合:

  1. V2 名前空間を作成します: App\Http\Controllers\Tasks\V2\
  2. リソースファイルに V2 ルートグループを追加します
  3. V1 ルートに Sunset ミドルウェアを追加します:
Route::middleware(['auth:api', 'http.sunset:2025-12-31'])->group(function () {
    // V1 routes
});

認証のセットアップ

PHP Open Source Saver JWT パッケージを使用します。

composer require php-open-source-saver/jwt-auth
php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret

config/auth.php で設定します。

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

必須のセットアップ

app/Providers/AppServiceProvider.php に追加します。

use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::shouldBeStrict(); // Prevent N+1 queries
}

app/Http/Kernel.php に HttpSunset ミドルウェアを登録します。

protected $middlewareAliases = [
    'http.sunset' => \App\Http\Middleware\HttpSunset::class,
];

避けるべきアンチパターン

  • ULID の代わりに自動インクリメント ID を使用する
  • モデルにビジネスロジックを書く
  • 1 つのコントローラーに複数のアクションを書く
  • コントローラーやアクションでリクエストデータに直接アクセスする
  • 隠れたクエリスコープ
  • Action で十分な場面でサービスクラスを使う
  • バージョニングなしの破壊的変更
  • 一貫性のないレスポンス形式
  • ネストした三項演算子(代わりに match 式を使用してください)
  • メソッドとパラメータの型宣言の欠如
  • 可読性を犠牲にした過度に簡潔な「巧みな」コード

コードレビューとリファクタリング

Laravel API のコードをレビューまたはリファクタリングする場合、次の原則を適用します。

簡素化チェックリスト
  1. 機能を維持する - リファクタリングが動作を変えないことを確認する
  2. 型安全性を確認する - 不足している戻り値の型と引数の型を追加する
  3. ロジックを簡素化する - ネストした三項演算子を match 式に置き換える
  4. 複雑さを抽出する - 複雑な条件を名前付きメソッドに移動する
  5. 標準に準拠する - declare(strict_types=1) を使用して PSR-12 への準拠を確認する
  6. 命名を改善する - 意図が伝わる説明的な名前を使用する
match 式のパターン

明確さのために、ネストした三項演算子を match に置き換えます:

// ❌ Avoid: Nested ternary
$status = $task->completed_at 
    ? ($task->verified ? 'verified' : 'completed')
    : ($task->started_at ? 'in_progress' : 'pending');

// ✅ Prefer: Match expression
$status = match (true) {
    $task->completed_at && $task->verified => 'verified',
    $task->completed_at => 'completed',
    $task->started_at => 'in_progress',
    default => 'pending',
};

参考資料

  • architecture.md - 包括的なアーキテクチャパターンと原則
  • code-examples.md - すべてのコンポーネントの完全な動作例
  • code-quality.md - Laravel のベストプラクティス、リファクタリングパターン、PSR-12 標準

テンプレート

すばやく雛形を生成するための assets/templates/ 内のテンプレートファイル:

  • Controller.php
  • FormRequest.php
  • Payload.php
  • Action.php
  • Model.php

コメント