LaravelでRESTful APIを構築する前に
RESTful APIはリソース指向の設計思想に基づくWebAPIの設計スタイルです。LaravelはAPIルーティング・認証・レート制限・JSON応答など、RESTful API構築に必要な機能を標準で備えています。
本記事では実務で使えるAPI設計のベストプラクティスとLaravelでの実装方法を解説します。
ルーティングの設計
// routes/api.php
use App\Http\Controllers\Api\V1\PostController;
use App\Http\Controllers\Api\V1\AuthController;
Route::prefix('v1')->name('api.v1.')->group(function () {
// 認証不要エンドポイント
Route::post('/auth/login', [AuthController::class, 'login'])->name('auth.login');
Route::post('/auth/register', [AuthController::class, 'register'])->name('auth.register');
// 公開リソース(読み取りのみ)
Route::apiResource('posts', PostController::class)->only(['index', 'show']);
// 認証必須
Route::middleware('auth:sanctum')->group(function () {
Route::post('/auth/logout', [AuthController::class, 'logout']);
Route::apiResource('posts', PostController::class)
->except(['index', 'show']);
Route::apiResource('comments', CommentController::class);
});
});
JSON応答の統一——API Resourceクラス
php artisan make:resource PostResource でAPI Resourceを作成します。
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'type' => 'post',
'attributes' => [
'title' => $this->title,
'excerpt' => $this->excerpt,
'body' => $this->when(
$request->routeIs('*.show'), // 詳細取得のみbodyを含む
$this->body
),
'category' => $this->category,
'published_at' => $this->published_at?->toIso8601String(),
'created_at' => $this->created_at->toIso8601String(),
],
'relationships' => [
'author' => new UserResource($this->whenLoaded('author')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
],
];
}
}
// コントローラー
public function index(Request $request): JsonResponse
{
$posts = Post::with(['author', 'tags'])
->published()
->paginate($request->integer('per_page', 15));
return PostResource::collection($posts)->response();
}
public function show(Post $post): PostResource
{
$post->load(['author', 'tags', 'comments']);
return new PostResource($post);
}
統一されたエラーレスポンス
// app/Exceptions/Handler.php
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
public function register(): void
{
$this->renderable(function (ModelNotFoundException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'error' => [
'code' => 'NOT_FOUND',
'message' => 'リソースが見つかりません',
],
], 404);
}
});
$this->renderable(function (ValidationException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'error' => [
'code' => 'VALIDATION_ERROR',
'message' => 'バリデーションエラー',
'details' => $e->errors(),
],
], 422);
}
});
}
認証——Laravel Sanctum
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
class AuthController extends Controller
{
public function login(LoginRequest $request): JsonResponse
{
$credentials = $request->only('email', 'password');
if (!Auth::attempt($credentials)) {
return response()->json([
'error' => ['code' => 'UNAUTHORIZED', 'message' => 'メールアドレスまたはパスワードが正しくありません'],
], 401);
}
$user = Auth::user();
// 既存トークンを削除して新しいトークンを発行
$user->tokens()->delete();
$token = $user->createToken('api-token', ['*'], now()->addDays(30));
return response()->json([
'data' => [
'token' => $token->plainTextToken,
'expires_at' => $token->accessToken->expires_at?->toIso8601String(),
'user' => new UserResource($user),
],
]);
}
public function logout(Request $request): JsonResponse
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'ログアウトしました']);
}
}
レート制限
// app/Providers/RouteServiceProvider.php(または bootstrap/app.php)
RateLimiter::for('api', function (Request $request) {
return [
// 認証済みユーザーは1分間に60リクエスト
Limit::perMinute(60)->by($request->user()?->id),
// 未認証は1分間に20リクエスト(IPアドレスベース)
Limit::perMinute(20)->by($request->ip()),
];
});
// ログインエンドポイントは厳しく制限
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
ページネーションのカスタマイズ
// カーソルベースページネーション(大量データに向いている)
public function index(): JsonResponse
{
$posts = Post::published()
->orderBy('id')
->cursorPaginate(20);
return response()->json([
'data' => PostResource::collection($posts->items()),
'meta' => [
'next_cursor' => $posts->nextCursor()?->encode(),
'prev_cursor' => $posts->previousCursor()?->encode(),
'per_page' => $posts->perPage(),
'has_more' => $posts->hasMorePages(),
],
]);
}
APIドキュメント——Scribe
composer require --dev knuckleswtf/scribe
php artisan scribe:generate
コントローラーにPHPDocを書くだけで自動的にAPIドキュメントを生成できます。
まとめ
LaravelのAPI Resource・Sanctum認証・レート制限を組み合わせることで、本番運用に耐えるRESTful APIをすばやく構築できます。弊社では予約管理システム・ECバックエンドなどのAPI開発実績があります。
LaravelによるAPI開発のご相談はお気軽にどうぞ。