PHPで学ぶAPIのベストプラクティス

PHPで学ぶAPIのベストプラクティス

目次

学習の経緯

個人開発でフロントエンドとバックエンドを分離した構成を試みたとき、「API設計って何から考えればいいんだろう」と迷った経験があります。最初はなんとなくエンドポイントを作っていましたが、規模が大きくなるにつれて命名が揺れたり、エラーの返し方がバラバラになったりと、メンテナンスが辛くなっていきました。この記事では、PHPでAPIを実装する際に意識すべきベストプラクティスをまとめます。

RESTful APIとは何か

REST(Representational State Transfer)は、HTTPプロトコルの特性(メソッド、URL、ステータスコード)を活かしてリソースを操作する設計思想です。ポイントは「URLに動詞を入れない」こと。

GET    /users       → ユーザー一覧を取得
POST   /users       → ユーザーを作成
GET    /users/1     → ID=1のユーザーを取得
PUT    /users/1     → ID=1のユーザーを更新
DELETE /users/1     → ID=1のユーザーを削除

エンドポイント設計のベストプラクティス

  • リソース名は複数形の名詞で統一する(/users, /products
  • 階層関係は / で表現する(/users/1/orders
  • バージョニングはURLに含める(/api/v1/users
// ルーティング例(Slimフレームワーク)
$app->get('/api/v1/users', [UserController::class, 'index']);
$app->post('/api/v1/users', [UserController::class, 'store']);
$app->put('/api/v1/users/{id}', [UserController::class, 'update']);
$app->delete('/api/v1/users/{id}', [UserController::class, 'destroy']);

レスポンス形式とステータスコード

サーバーからクライアントへのレスポンスは、以下の表のようなステータスコードがあります。
これらを使い、適切にサーバーから返すことでクライアント側から見て分かりやすいレスポンスとなります。

コード意味使いどころ
200OK取得・更新成功
201Created作成成功
400Bad Requestバリデーションエラー
401Unauthorized未認証
403Forbidden権限なし
404Not Foundリソース未存在
500Internal Server Errorサーバーエラー
<?php
function jsonResponse(array $data, int $status = 200): void {
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    exit;
}

// エラーレスポンス例
jsonResponse(['status' => 'error', 'message' => 'ユーザーが見つかりません', 'code' => 'USER_NOT_FOUND'], 404);

認証:Bearer Token / JWT

JWTは「ヘッダー.ペイロード.署名」の3パーツをBase64URLエンコードしたトークンで、サーバー側にセッションを持たずに認証できます。

<?php
function authenticateRequest(): array {
    $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (!str_starts_with($authHeader, 'Bearer ')) {
        jsonResponse(['status' => 'error', 'message' => '認証トークンがありません'], 401);
    }
    $token = substr($authHeader, 7);
    $decoded = JWT::decode($token, new Key(SECRET_KEY, 'HS256'));
    return (array) $decoded;
}

APIキー認証の場合は hash_equals() を使ってタイミング攻撃を防ぐことが重要です。

バリデーションとエラーハンドリング

ログイン画面やフォーム画面など、ユーザーの入力に対する判定をバリデーションといいます。

<?php
function validateUserInput(array $input): array {
    $errors = [];
    if (empty($input['name'])) {
        $errors['name'] = '名前は必須です';
    } elseif (mb_strlen($input['name']) > 50) {
        $errors['name'] = '名前は50文字以内で入力してください';
    }
    if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'メールアドレスの形式が正しくありません';
    }
    return $errors;
}

レート制限の考え方

Redisを使ったシンプルなレート制限:

<?php
function checkRateLimit(string $identifier, int $limit = 60, int $window = 60): void {
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = "rate_limit:{$identifier}";
    $current = $redis->incr($key);
    if ($current === 1) { $redis->expire($key, $window); }
    if ($current > $limit) {
        jsonResponse(['status' => 'error', 'message' => 'リクエスト制限を超えました'], 429);
    }
}

まとめ

  • URLはリソース名(複数形名詞)で設計し、動詞は使わない
  • HTTPメソッドとステータスコードを意味通りに使う
  • 認証にはJWT or APIキーを使い、タイミング攻撃を防ぐ
  • 入力値は必ずバリデーションしてからDBに渡す
  • レート制限を設けて乱用を防ぐ
  • エラーの詳細はログに残し、外部には漏らさない
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次