laravel-api について
laravel-apiは、Laravel REST APIを構築する開発スキルです。新規APIの実装や既存コードのレビューに取り組む開発者向けに、ステートレス設計、リソース単位の構成、明確なレイヤー分けと、実装指針やテンプレートを提供します。
主な機能
- 01リソース単位でルートとコントローラーを構成
- 02ステートレスな多層アーキテクチャの原則
- 03CRUDエンドポイント構築の手順
- 04APIバージョン管理とレスポンス形式の標準化
- 05コンポーネントテンプレートとコードレビュー指針
活用シーン
- 01LaravelプロジェクトでREST APIを構築
- 02リソースにバージョン管理されたCRUDを追加
- 03APIコードの一貫性を保つリファクタリングやレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ juststeveking/ laravel-api-skill --skill laravel-apiインストール後、Claude Code で/laravel-apiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/templates/Action.phpController.phpFormRequest.phpModel.phpPayload.phpreferences/architecture.mdcode-examples.mdcode-quality.md詳細
Laravel API - Steve's Architecture
クリーンでステートレス、リソース単位のスコープを持つアーキテクチャで Laravel REST API を構築します。
クイックスタート
ユーザーが Laravel API をリクエストした場合、次のワークフローに従います。
- 要件を理解する - どのようなリソースが必要ですか?どのような操作ですか?認証は必要ですか?
- プロジェクト構造を初期化する - ルーティングを設定し、フロントエンドの不要な部分を削除します
- 最初のリソースを構築する - パターンを確立するために完全な CRUD を実装します
- 認証を追加する - PHP Open Source Saver による JWT
- 残りのリソースを反復開発する - 確立したパターンに従います
コアアーキテクチャの原則
詳細については references/architecture.md をお読みください。主要な原則は次のとおりです。
- 設計によるステートレス - 隠れた依存関係がなく、明示的なデータフロー
- 境界優先 - HTTP、ビジネスロジック、データ層の明確な分離
- リソース単位のスコープ - ルートとコントローラーをリソースごとに整理
- バージョン規律 - 名前空間ベースのバージョニング、HTTP Sunset ヘッダー
コード品質基準
すべてのコードは Laravel のベストプラクティスと PSR-12 標準に従う必要があります。
- 機能を保持する - リファクタリングはコードの「動作の仕方」を変えるものであり、「動作そのもの」を変えてはなりません
- 暗黙より明示 - 巧みなショートカットよりも、明確で読みやすいコードを優先します
- 型宣言 - メソッドには必ず戻り値の型を、パラメータには有益な場合に型を付けます
- ネストした三項演算子を避ける - 明確さのために match 式、switch、または if/else を使用します
- 一貫した命名 - PSR-12 と Laravel の規約に厳密に従います
- 適切な名前空間 - インポートを論理的に整理し、完全な型ヒントを使用します
コードをレビューまたはリファクタリングする場合:
- 巧みさよりも明確さと保守性に焦点を当てます
- 複雑なネストしたロジックを読みやすい構造に単純化します
- マジック値を名前付き定数または設定に抽出します
- 正確な動作を保ちながら不要な複雑さを削除します
プロジェクト構造
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 を作成する場合:
- V2 名前空間を作成します:
App\Http\Controllers\Tasks\V2\ - リソースファイルに V2 ルートグループを追加します
- 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 のコードをレビューまたはリファクタリングする場合、次の原則を適用します。
簡素化チェックリスト
- 機能を維持する - リファクタリングが動作を変えないことを確認する
- 型安全性を確認する - 不足している戻り値の型と引数の型を追加する
- ロジックを簡素化する - ネストした三項演算子を match 式に置き換える
- 複雑さを抽出する - 複雑な条件を名前付きメソッドに移動する
- 標準に準拠する - declare(strict_types=1) を使用して PSR-12 への準拠を確認する
- 命名を改善する - 意図が伝わる説明的な名前を使用する
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