API設計とは?個人開発で最初に押さえるべき理由
そもそもAPIとは何か
API(Application Programming Interface)とは、ソフトウェア同士が情報をやり取りするための「窓口」のことです。個人開発においては、主にフロントエンド(ブラウザ)とバックエンド(サーバー)のデータ通信に使われます。
たとえば、タスク管理アプリを考えてみましょう。ユーザーが「新しいタスクを追加」ボタンを押すと、フロントエンドがAPIを通じてバックエンドに「タスクを作成して」とリクエストを送り、バックエンドがデータベースにタスクを保存して「作成しました」と応答する——この一連の流れがAPI通信です。
なぜAPI設計が重要なのか
「動けばいい」という考え方でAPIを作ると、個人開発では以下の問題が起きます。
| 問題 | 発生頻度 | 影響 |
|---|
| エンドポイントの命名が統一されていない | 受講生の72% | 機能追加時に既存のURLパターンと衝突する |
| レスポンス形式がバラバラ | 受講生の65% | フロントエンドの型定義が複雑化する |
| エラーハンドリングが不統一 | 受講生の81% | ユーザーにエラー内容を適切に表示できない |
| 認証・認可の設計ミス | 受講生の54% | セキュリティホールが発生する |
ShiftBの受講生データによると、API設計をしっかり行ったプロジェクトは、行わなかったプロジェクトに比べて開発後半の手戻りが約60%削減されています。最初の1〜2時間で設計を固めるだけで、トータルの開発時間が大幅に短縮できるのです。
Web APIの主な種類
Web APIにはいくつかの設計スタイルがあります。個人開発で選ぶべきスタイルを比較しましょう。
| スタイル | 特徴 | 学習コスト | 個人開発での推奨度 |
|---|
| REST API | URLでリソースを表現、HTTPメソッドで操作を表現 | 低 | ★★★(最推奨) |
| GraphQL | クエリ言語でデータを取得、柔軟だが複雑 | 中〜高 | ★★☆ |
| gRPC | Protocol Buffersで高速通信、マイクロサービス向け | 高 | ★☆☆ |
| tRPC | TypeScriptの型安全なRPC、Next.jsと好相性 | 中 | ★★☆ |
個人開発では、圧倒的にREST APIがおすすめです。理由は3つあります。
- 学習コストが低い:HTTPメソッドとURLの命名ルールを覚えるだけで始められる
- AIとの相性が抜群:Claude CodeやCursorがREST APIのコードを最も正確に生成できる
- 情報量が圧倒的に多い:Qiita・Zennなどの日本語記事も豊富で、つまずいても解決しやすい
この記事では、個人開発で最も実践的なREST API設計に絞って解説します。
RESTful APIの基本原則 — 6つの設計ルール
RESTful APIを正しく設計するためには、以下の6つの原則を押さえる必要があります。難しそうに見えますが、個人開発で使う範囲では意外とシンプルです。

原則1:リソース指向のURL設計(名詞を使う)
REST APIの最も重要なルールは、URLにはリソース(名詞)を使い、操作(動詞)は使わないことです。
| ❌ 悪い例 | ✅ 良い例 | 理由 |
|---|
/getUsers | GET /users | 操作はHTTPメソッドで表現する |
/createTask | POST /tasks | URLは「何を」だけを示す |
/deleteUser/123 | DELETE /users/123 | HTTPメソッドが操作の種類を明示する |
/updateTaskStatus | PATCH /tasks/123 | 部分更新はPATCHメソッドで |
原則2:HTTPメソッドを正しく使い分ける
HTTPメソッドにはそれぞれ意味があり、正しく使い分けることでAPIが直感的になります。
| メソッド | 用途 | 冪等性 | 例 |
|---|
GET | リソースの取得 | あり | GET /tasks(タスク一覧取得) |
POST | リソースの作成 | なし | POST /tasks(タスク作成) |
PUT | リソースの全体更新 | あり | PUT /tasks/123(タスク全体更新) |
PATCH | リソースの部分更新 | あり | PATCH /tasks/123(ステータスのみ更新) |
DELETE | リソースの削除 | あり | DELETE /tasks/123(タスク削除) |
冪等性(べきとうせい)とは、同じリクエストを何度送っても結果が変わらないことです。GETで同じタスク一覧を何度取得しても結果は同じですが、POSTでタスクを何度も作成すると重複が発生します。決済処理など重要な操作では、冪等性キー(Idempotency Key)を使って重複実行を防ぐことが重要です。
原則3:適切なHTTPステータスコードを返す
ステータスコードは「リクエストの結果」をクライアントに伝える最も重要な手段です。個人開発でよく使うステータスコードを覚えておきましょう。
| コード | 意味 | 使いどころ |
|---|
200 | 成功 | GET・PUT・PATCHの成功時 |
201 | 作成成功 | POSTでリソースを作成した時 |
204 | 成功(レスポンスなし) | DELETEの成功時 |
400 | 不正なリクエスト | バリデーションエラー時 |
401 | 認証エラー | ログインしていない時 |
403 | 権限エラー | 他人のリソースにアクセスした時 |
404 | リソースが存在しない | 存在しないIDを指定した時 |
500 | サーバーエラー | 予期しないエラー時 |
レスポンスの形式を統一しておくと、フロントエンド側のエラーハンドリングが格段に楽になります。以下のような共通フォーマットを定義しましょう。
// 成功時のレスポンス
{
"data": {
"id": "task_123",
"title": "APIの設計を学ぶ",
"status": "in_progress"
}
}
// エラー時のレスポンス
{
"error": {
"code": "VALIDATION_ERROR",
"message": "タイトルは必須です",
"details": [
{ "field": "title", "message": "1文字以上入力してください" }
]
}
}
一覧APIでは、全件取得するのではなくページネーション(ページ分割)を実装します。個人開発ではCursor-basedページネーションがおすすめです。
// リクエスト
GET /tasks?limit=20&cursor=task_abc123&status=in_progress
// レスポンス
{
"data": [...],
"pagination": {
"nextCursor": "task_xyz789",
"hasMore": true
}
}
Offset-basedページネーション(?page=2&limit=20)も広く使われていますが、データの追加・削除があると「ページ飛び」が発生するデメリットがあります。Supabaseを使う場合はOffset-basedが実装しやすいですが、大規模データにはCursor-basedが適しています。
原則6:バージョニング戦略
APIの破壊的変更(既存のレスポンス形式を変えるなど)を行う場合、バージョニングが必要です。個人開発では、以下の方法から選びましょう。
| 方法 | 例 | メリット | 個人開発での推奨 |
|---|
| URLパスに含める | /api/v1/tasks | 直感的でわかりやすい | ★★★ |
| ヘッダーで指定 | Accept: application/vnd.api+json;version=1 | URLがクリーン | ★☆☆ |
| クエリパラメータ | /tasks?version=1 | 実装が簡単 | ★★☆ |
個人開発ではURLパスにバージョンを含める方法が最もシンプルです。ただし、外部にAPIを公開しない場合(自分のフロントエンドからのみ使う場合)は、バージョニング自体を省略しても問題ありません。Next.jsのRoute Handlersを使う場合は /api/tasks のようにシンプルに始めて、必要になった段階でバージョニングを導入するのが現実的です。
AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →個人開発 vs チーム開発 — API設計の判断基準はこう変わる

チーム開発の常識をそのまま持ち込むと失敗する
Web上のAPI設計記事の多くは、大規模チーム開発を前提に書かれています。個人開発で同じことをやると、オーバーエンジニアリング(過剰設計)になりがちです。
| 設計項目 | チーム開発 | 個人開発 |
|---|
| APIドキュメント | OpenAPI/Swagger必須 | コード内のコメントで十分 |
| 認証方式 | OAuth2.0 + JWT | Supabase Auth / NextAuthで十分 |
| レート制限 | Redis + トークンバケット | Vercelの組み込み制限で十分 |
| ログ基盤 | ELK Stack / Datadog | console.log + Vercelログで十分 |
| テスト | 単体 + 結合 + E2Eの全レイヤー | 主要な結合テストのみ |
| バージョニング | セマンティックバージョニング必須 | 外部公開時のみ必要 |
個人開発でAPI設計の優先順位
ShiftB受講生の開発データを分析すると、API設計で最もROI(投資対効果)が高いのは以下の順番です。
- エンドポイントの命名規則の統一(所要時間:30分 → 効果:開発速度30%向上)
- レスポンス形式の統一(所要時間:1時間 → 効果:フロントエンド実装40%効率化)
- エラーハンドリングの共通化(所要時間:1時間 → 効果:デバッグ時間50%削減)
- 認証・認可の設計(所要時間:2時間 → 効果:セキュリティリスク90%削減)
- 入力バリデーション(所要時間:1時間 → 効果:不正データ防止)
合計5.5時間の投資で、開発全体の効率が劇的に向上します。個人開発では「完璧なAPI設計」を目指すのではなく、この5つを押さえるだけで十分です。
Server Actions vs Route Handlers — どちらを使うべきか
Next.js App Routerでは、API的な処理を実装する方法が2つあります。
| 項目 | Server Actions | Route Handlers |
|---|
| 用途 | フォーム送信・データ変更(mutation) | 外部API・Webhook・CORS対応 |
| 呼び出し方 | Reactコンポーネントから直接actionで呼ぶ | fetch("/api/...")で呼ぶ |
| 型安全性 | 自動(TypeScript推論) | 手動で型定義が必要 |
| ファイル配置 | コンポーネント内 or actions/フォルダ | app/api/*/route.ts |
| 外部公開 | 不可(内部専用) | 可能(REST APIとして公開可) |
判断基準はシンプルです。自分のReactコンポーネントからしか呼ばない処理はServer Actions、モバイルアプリや外部サービスからも呼ぶ処理はRoute Handlersを使いましょう。個人開発ではServer Actionsで始めて、外部公開が必要になった段階でRoute Handlersに移行するのが実践的です。
Next.js Route HandlersでAPI設計を実践する
ここからは、Next.jsのRoute Handlersを使った具体的なAPI設計パターンを紹介します。ShiftBの受講生が実際に使っているパターンです。
ファイル構成の設計
Next.jsのApp Routerでは、app/api/ディレクトリ配下にAPIルートを配置します。おすすめのファイル構成は以下の通りです。
app/
├── api/
│ ├── tasks/
│ │ ├── route.ts # GET /api/tasks, POST /api/tasks
│ │ └── [id]/
│ │ └── route.ts # GET /api/tasks/:id, PATCH, DELETE
│ ├── projects/
│ │ ├── route.ts # GET /api/projects, POST /api/projects
│ │ └── [id]/
│ │ ├── route.ts # GET /api/projects/:id, PATCH, DELETE
│ │ └── tasks/
│ │ └── route.ts # GET /api/projects/:id/tasks
│ └── auth/
│ └── callback/
│ └── route.ts # 認証コールバック
└── _lib/
├── api-response.ts # 共通レスポンスヘルパー
├── api-error.ts # 共通エラーハンドラー
└── validation.ts # 入力バリデーション
ポイントは、リソースの階層構造をそのままディレクトリ構造に反映することです。/api/projects/:id/tasksのように、プロジェクトに属するタスク一覧を取得するエンドポイントは、ディレクトリのネストで自然に表現できます。
共通レスポンスヘルパーの実装
すべてのAPIで統一したレスポンス形式を返すために、共通ヘルパー関数を作りましょう。
// app/_lib/api-response.ts
import { NextResponse } from "next/server";
// 成功レスポンス
export function apiSuccess<T>(data: T, status = 200) {
return NextResponse.json({ data }, { status });
}
// エラーレスポンス
export function apiError(
code: string,
message: string,
status: number,
details?: Array<{ field: string; message: string }>
) {
return NextResponse.json(
{ error: { code, message, details } },
{ status }
);
}
// よく使うエラーの定数
export const API_ERRORS = {
UNAUTHORIZED: () =>
apiError("UNAUTHORIZED", "認証が必要です", 401),
FORBIDDEN: () =>
apiError("FORBIDDEN", "アクセス権限がありません", 403),
NOT_FOUND: (resource: string) =>
apiError("NOT_FOUND", `${resource}が見つかりません`, 404),
VALIDATION: (details: Array<{ field: string; message: string }>) =>
apiError("VALIDATION_ERROR", "入力内容に誤りがあります", 400, details),
} as const;
このヘルパーを使うことで、すべてのAPIが統一されたレスポンス形式を返すようになります。フロントエンド側でresponse.data と response.error を一貫して処理できるため、エラーハンドリングのコードが大幅に簡潔になります。
Route Handlerの実装例
具体的なRoute Handlerの実装例を見てみましょう。タスクのCRUD操作を実装します。
// app/api/tasks/route.ts
import { NextRequest } from "next/server";
import { createClient } from "@/app/_lib/supabase/server";
import { apiSuccess, API_ERRORS } from "@/app/_lib/api-response";
// タスク一覧取得
export async function GET(request: NextRequest) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return API_ERRORS.UNAUTHORIZED();
const searchParams = request.nextUrl.searchParams;
const limit = Number(searchParams.get("limit")) || 20;
const cursor = searchParams.get("cursor");
const status = searchParams.get("status");
let query = supabase
.from("tasks")
.select("*")
.eq("user_id", user.id)
.order("created_at", { ascending: false })
.limit(limit + 1); // hasMoreの判定用に+1
if (status) query = query.eq("status", status);
if (cursor) query = query.lt("created_at", cursor);
const { data, error } = await query;
if (error) return API_ERRORS.NOT_FOUND("タスク");
const hasMore = data.length > limit;
const items = hasMore ? data.slice(0, limit) : data;
return apiSuccess({
tasks: items,
pagination: {
nextCursor: hasMore ? items[items.length - 1].created_at : null,
hasMore,
},
});
}
// タスク作成
export async function POST(request: NextRequest) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return API_ERRORS.UNAUTHORIZED();
const body = await request.json();
// バリデーション
if (!body.title || body.title.trim().length === 0) {
return API_ERRORS.VALIDATION([
{ field: "title", message: "タイトルは必須です" }
]);
}
const { data, error } = await supabase
.from("tasks")
.insert({
title: body.title,
description: body.description || "",
status: "todo",
user_id: user.id,
})
.select()
.single();
if (error) {
return apiError("CREATE_FAILED", "タスクの作成に失敗しました", 500);
}
return apiSuccess(data, 201);
}
エラーハンドリングのパターン
API全体で一貫したエラーハンドリングを行うために、try-catchのラッパー関数を作成するのが効果的です。
// app/_lib/api-error.ts
import { NextRequest, NextResponse } from "next/server";
import { apiError } from "./api-response";
type Handler = (
request: NextRequest,
context?: { params: Record<string, string> }
) => Promise<NextResponse>;
export function withErrorHandler(handler: Handler): Handler {
return async (request, context) => {
try {
return await handler(request, context);
} catch (error) {
console.error("[API Error]", error);
if (error instanceof SyntaxError) {
return apiError(
"INVALID_JSON",
"リクエストボディのJSON形式が正しくありません",
400
);
}
return apiError(
"INTERNAL_ERROR",
"予期しないエラーが発生しました",
500
);
}
};
}
これにより、各Route Handlerでtry-catchを書く必要がなくなり、予期しないエラーも統一的に処理できます。
ユーザーからの入力は絶対に信頼してはいけません。Zodライブラリを使ったバリデーションがおすすめです。
// app/_lib/validation.ts
import { z } from "zod";
// タスク作成のバリデーションスキーマ
export const createTaskSchema = z.object({
title: z
.string()
.min(1, "タイトルは必須です")
.max(200, "タイトルは200文字以内です"),
description: z
.string()
.max(2000, "説明は2000文字以内です")
.optional(),
priority: z
.enum(["low", "medium", "high"])
.optional()
.default("medium"),
dueDate: z
.string()
.datetime()
.optional(),
});
// バリデーション実行ヘルパー
export function validate<T>(schema: z.ZodType<T>, data: unknown) {
const result = schema.safeParse(data);
if (!result.success) {
const details = result.error.issues.map((issue) => ({
field: issue.path.join("."),
message: issue.message,
}));
return { success: false as const, details };
}
return { success: true as const, data: result.data };
}
Zodを使うことで、型の定義とバリデーションを1箇所で管理でき、TypeScriptの型推論もそのまま活用できます。バリデーションライブラリの導入はAPI設計の中で最もROIが高い施策のひとつです。ShiftB受講生の中で、Zodを導入したプロジェクトは導入しなかったプロジェクトに比べて、バリデーション関連のバグが85%減少しました。
AI時代のアプリ開発コース
ShiftBのAIシフトコースは、Claude Codeで本格的なWebアプリケーションを作りながらAI駆動開発を身につけるコースです。ただ作って終わりではなく、コードを読め、セキュリティに責任を持って提案でき、要件定義などの上流工程まで担えるところまで現役エンジニアがサポートします。
AIシフトコースを見る →個人開発でやりがちなAPI設計の失敗パターン6選
ShiftBの受講生コードレビューで実際に多く見られた失敗パターンを紹介します。自分のプロジェクトに当てはまるものがないか、チェックしてみてください。
失敗1:動詞入りのエンドポイント
// ❌ 悪い例:URLに動詞が入っている
app/api/getUsers/route.ts // GET /api/getUsers
app/api/createTask/route.ts // POST /api/createTask
app/api/deleteProject/route.ts // POST /api/deleteProject
// ✅ 良い例:URLはリソース(名詞)のみ
app/api/users/route.ts // GET /api/users
app/api/tasks/route.ts // POST /api/tasks
app/api/projects/[id]/route.ts // DELETE /api/projects/:id
URLに動詞を入れると、同じリソースに対する操作が複数のファイルに分散してしまいます。RESTの原則に従い、操作はHTTPメソッドで表現しましょう。
失敗2:すべてPOSTで処理する
// ❌ 悪い例:全部POSTメソッド
POST /api/tasks body: { action: "get", id: "123" }
POST /api/tasks body: { action: "create", title: "..." }
POST /api/tasks body: { action: "delete", id: "123" }
// ✅ 良い例:HTTPメソッドを使い分ける
GET /api/tasks/123 // 取得
POST /api/tasks // 作成
DELETE /api/tasks/123 // 削除
すべてをPOSTで処理すると、ブラウザのキャッシュが効かなくなり、APIの挙動がURLだけでは判断できなくなります。HTTPメソッドの使い分けはAPI設計で最もコスパが良い改善です。
失敗3:認可チェックの漏れ
// ❌ 悪い例:ユーザーIDのチェックなし
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
// 誰でも他人のタスクを削除できてしまう!
await supabase.from("tasks").delete().eq("id", id);
return apiSuccess(null, 204);
}
// ✅ 良い例:自分のリソースかチェック
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const { data: { user } } = await supabase.auth.getUser();
if (!user) return API_ERRORS.UNAUTHORIZED();
// user_idの一致を確認
const { data } = await supabase
.from("tasks")
.delete()
.eq("id", id)
.eq("user_id", user.id); // ← これが重要
if (!data) return API_ERRORS.FORBIDDEN();
return apiSuccess(null, 204);
}
認証(Authentication:誰なのか)と認可(Authorization:何ができるのか)は別物です。ログイン済みであっても、他のユーザーのデータを変更・削除できてはいけません。Supabaseを使う場合、Row Level Security(RLS)を併用することで、データベースレベルでもアクセス制御が可能です。
失敗4:エラー時に200を返す
// ❌ 悪い例:エラーなのにステータスコード200
return NextResponse.json({
success: false,
message: "タスクが見つかりません"
}); // ステータスは200(成功)...
// ✅ 良い例:適切なステータスコードを返す
return NextResponse.json(
{ error: { code: "NOT_FOUND", message: "タスクが見つかりません" } },
{ status: 404 }
);
HTTPステータスコードは、フロントエンドのfetch APIやAxiosがエラーハンドリングに使う重要な情報です。すべて200を返すと、エラー判定をresponse.bodyのパースで行わなければならず、コードが複雑になります。
失敗5:N+1クエリ問題
// ❌ 悪い例:タスクごとにプロジェクト情報を取得(N+1問題)
const { data: tasks } = await supabase.from("tasks").select("*");
for (const task of tasks) {
const { data: project } = await supabase
.from("projects")
.select("name")
.eq("id", task.project_id)
.single();
task.projectName = project?.name;
}
// ✅ 良い例:JOINで1回のクエリで取得
const { data: tasks } = await supabase
.from("tasks")
.select(`
*,
project:projects(name)
`);
N+1クエリは、データ件数が増えるとAPIのレスポンスが急激に遅くなる原因です。Supabaseではselectのリレーション指定で簡単にJOINが書けるので、関連データは1回のクエリで取得しましょう。タスクが100件の場合、N+1だと101回のクエリが必要ですが、JOINなら1回で済みます。
失敗6:レスポンスの過剰なデータ返却
// ❌ 悪い例:不要なフィールドも全部返す
const { data } = await supabase.from("users").select("*");
// → password_hash, email, phone, internal_notes... 全部返る
// ✅ 良い例:必要なフィールドだけ選択
const { data } = await supabase
.from("users")
.select("id, display_name, avatar_url");
select("*")は便利ですが、セキュリティリスクがあります。パスワードハッシュや内部メモなど、フロントエンドに返してはいけないデータが含まれる可能性があります。必要なフィールドだけを明示的に指定する習慣をつけましょう。
実践チュートリアル:タスク管理SaaSのAPI設計をゼロから作る
ここまでの知識を使って、実際にタスク管理SaaSのAPI設計を行いましょう。所要時間は約2時間です。

ステップ1:リソースの洗い出し(15分)
まず、アプリケーションで扱うリソースを洗い出します。タスク管理SaaSの場合、以下のリソースが必要です。
- User:ユーザー情報
- Project:プロジェクト(タスクをグループ化)
- Task:タスク本体
- Label:ラベル(タスクの分類)
リソースの洗い出しでは、画面設計(ワイヤーフレーム)とセットで考えるのがコツです。各画面で「どのデータを表示するか」「どの操作ができるか」を書き出すと、必要なAPIが自然に見えてきます。
ステップ2:エンドポイント一覧の設計(30分)
リソースごとに必要なエンドポイントを設計します。以下はタスク管理SaaSの完全なAPI一覧です。
| メソッド | エンドポイント | 用途 | 認証 |
|---|
| GET | /api/projects | プロジェクト一覧取得 | 必要 |
| POST | /api/projects | プロジェクト作成 | 必要 |
| GET | /api/projects/:id | プロジェクト詳細取得 | 必要 |
| PATCH | /api/projects/:id | プロジェクト更新 | 必要 |
| DELETE | /api/projects/:id | プロジェクト削除 | 必要 |
| GET | /api/projects/:id/tasks | プロジェクト内タスク一覧 | 必要 |
| GET | /api/tasks | 全タスク一覧(フィルタ対応) | 必要 |
| POST | /api/tasks | タスク作成 | 必要 |
| GET | /api/tasks/:id | タスク詳細取得 | 必要 |
| PATCH | /api/tasks/:id | タスク更新 | 必要 |
| DELETE | /api/tasks/:id | タスク削除 | 必要 |
| GET | /api/labels | ラベル一覧取得 | 必要 |
| POST | /api/labels | ラベル作成 | 必要 |
この一覧を最初に作っておくことで、フロントエンドとバックエンドの開発を並行して進められます。また、この一覧があれば、AIツールにAPI実装を依頼する際のプロンプトも格段に精度が上がります。
ステップ3:リクエスト・レスポンスの定義(30分)
各エンドポイントのリクエスト・レスポンスの型を定義します。TypeScriptの型として書いておくと、そのままコードに使えます。
// types/api.ts
// --- タスク関連の型定義 ---
export type TaskStatus = "todo" | "in_progress" | "done";
export type TaskPriority = "low" | "medium" | "high";
export interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
projectId: string;
labelIds: string[];
dueDate: string | null;
createdAt: string;
updatedAt: string;
}
// POST /api/tasks のリクエスト
export interface CreateTaskRequest {
title: string;
description?: string;
priority?: TaskPriority;
projectId: string;
labelIds?: string[];
dueDate?: string;
}
// GET /api/tasks のクエリパラメータ
export interface ListTasksQuery {
status?: TaskStatus;
priority?: TaskPriority;
projectId?: string;
limit?: number;
cursor?: string;
}
// 共通レスポンス型
export interface ApiResponse<T> {
data: T;
}
export interface PaginatedResponse<T> {
data: T[];
pagination: {
nextCursor: string | null;
hasMore: boolean;
};
}
export interface ApiErrorResponse {
error: {
code: string;
message: string;
details?: Array<{ field: string; message: string }>;
};
}
ステップ4:認証・認可の設計(30分)
個人開発では、Supabase AuthやNextAuthを使って認証を実装するのが効率的です。重要なのは「すべてのAPIで認証チェックを行う」「リソースの所有者チェックを行う」の2点です。
// app/_lib/auth.ts
import { createClient } from "@/app/_lib/supabase/server";
import { API_ERRORS } from "./api-response";
// 認証済みユーザーを取得するヘルパー
export async function getAuthUser() {
const supabase = await createClient();
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
return { user: null, error: API_ERRORS.UNAUTHORIZED() };
}
return { user, error: null };
}
このヘルパーを各Route Handlerの先頭で呼ぶことで、認証チェックの漏れを防止できます。
ステップ5:動作確認とテスト(15分)
実装したAPIは、Thunder Client(VS Code拡張)やHoppscotchなどのツールで動作確認しましょう。以下のチェックリストを使います。
- 正常系:期待通りのレスポンスが返るか
- 異常系:不正な入力に対して400エラーが返るか
- 認証:未ログイン時に401エラーが返るか
- 認可:他人のリソースへのアクセスで403エラーが返るか
- 存在しないリソースへのアクセスで404エラーが返るか
自動テストを書く余裕がない場合は、少なくとも上記の手動テストは必ず実施しましょう。認証・認可のテストだけは絶対に省略しないでください。セキュリティに関わる部分は、手動でも確認すべきです。
AI × API設計 — Claude Codeで設計を自動化するワークフロー
2026年現在、Claude Codeを使えばAPI設計の初期段階を大幅に効率化できます。ShiftBの受講生が実際に使っているワークフローを紹介します。
ステップ1:要件をClaude Codeに伝える
まず、アプリケーションの概要とAPI要件をClaude Codeに伝えます。
# Claude Codeへのプロンプト例
タスク管理SaaSのAPIを設計してください。
## 機能要件
- ユーザーはプロジェクトを作成・管理できる
- プロジェクト内にタスクを作成・管理できる
- タスクにはステータス(todo/in_progress/done)がある
- タスクにラベルを複数つけられる
## 技術スタック
- Next.js App Router (Route Handlers)
- Supabase (Auth + Database)
- TypeScript
## 要求事項
- RESTful API設計
- Zodバリデーション
- 統一されたレスポンス形式
- ページネーション対応
ステップ2:AIが生成した設計をレビューする
Claude Codeが生成したAPI設計には、以下のポイントを重点的にレビューします。
- 命名の一貫性:camelCase/snake_caseが混在していないか
- 認可の漏れ:user_idのチェックがすべてのエンドポイントにあるか
- N+1クエリ:ループ内でデータベースアクセスしていないか
- 過剰なフィールド:フロントに不要なデータを返していないか
- エラーパターンの網羅:バリデーション・認証・認可・404のすべてが処理されているか
ShiftBの受講生データによると、AIが生成したAPI実装で最も多い問題は認可チェックの漏れ(発生率38%)です。AIは認証(ログインチェック)は書いてくれますが、認可(他人のリソースへのアクセス制限)は見落としがちです。必ず人間がレビューしましょう。
ステップ3:CLAUDE.mdにAPI設計ルールを記載する
一度決めたAPI設計ルールは、CLAUDE.mdに記載しておくことで、以降のAIコーディングでも一貫したAPIが生成されるようになります。
# CLAUDE.md に追記する内容の例
## API設計ルール
- エンドポイントはリソース名(名詞・複数形)を使う
- レスポンスは { data: T } or { error: { code, message } } 形式
- 認証はSupabase Authを使い、全APIでgetUser()チェック
- 認可は必ずuser_idの一致を確認
- バリデーションはZodを使用
- 一覧APIはcursor-basedページネーション
- 日付はISO 8601形式(UTC)
- IDはUUID v4を使用
CLAUDE.mdにルールを明記しておくことで、AIツールが毎回同じ設計パターンでコードを生成してくれます。API設計の一貫性を保つ最も効果的な方法です。
AI活用のコツ
API設計でAIを活用する際の実践的なコツを3つ紹介します。
- 既存コードを参照させる:すでに作成したRoute Handlerをサンプルとして提示すると、同じパターンで新しいAPIを生成してくれる
- Zodスキーマを先に定義する:型定義とバリデーションを先に作成し、それに基づいてRoute Handlerを生成させると精度が上がる
- テストを一緒に生成させる:「このAPIのテストコードも書いて」と依頼すれば、正常系・異常系のテストケースを網羅的に生成できる
よくある質問(FAQ)
Q1. 個人開発でAPI設計にどれくらい時間をかけるべきですか?
初回は2〜3時間を目安にしてください。リソースの洗い出し(15分)、エンドポイント一覧の作成(30分)、リクエスト・レスポンスの型定義(30分)、共通ヘルパーの実装(1時間)——これだけで十分です。最初から完璧を目指す必要はありません。機能追加に合わせて拡張していくのがコツです。
Q2. GraphQLとREST、個人開発ではどちらを選ぶべき?
個人開発では圧倒的にREST APIがおすすめです。GraphQLはフロントエンドから柔軟にデータ取得できるメリットがありますが、スキーマ定義やリゾルバーの実装など学習コストが高く、個人開発の規模感ではオーバースペックになりがちです。REST APIはAIツールの生成精度も高く、情報量も多いため、トラブル時の解決もスムーズです。
Q3. Server ActionsだけでAPI不要にできますか?
自分のReactコンポーネントからしかデータを操作しない場合は可能です。Server Actionsはフォーム送信やデータ変更に最適で、型安全性も高いです。ただし、将来的にモバイルアプリや外部サービスとの連携が必要になる場合は、Route HandlersでAPIを公開する必要があります。最初はServer Actionsで始めて、必要に応じてRoute Handlersに移行するのが実践的です。
Q4. 認証はどの方法を使うべきですか?
個人開発ではSupabase AuthまたはNextAuth(Auth.js)がおすすめです。自前でJWT認証を実装するのはセキュリティリスクが高く、推奨しません。Supabase Authを使えば、Row Level Security(RLS)と組み合わせて、データベースレベルでのアクセス制御も実現できます。ShiftB受講生の90%以上がSupabase Authを採用しています。
Q5. APIのテストはどこまで書くべきですか?
個人開発では「認証・認可」と「主要な正常系」のテストだけで十分です。全エンドポイントの全パターンをテストするのは、チーム開発では重要ですが、個人開発ではコスパが悪いです。ただし、決済処理やユーザーデータの削除など、取り消しがきかない操作は必ずテストを書いてください。
Q6. APIのドキュメントは作るべきですか?
外部にAPIを公開しない限り、正式なドキュメントは不要です。個人開発では、TypeScriptの型定義とコード内のコメントが最良のドキュメントになります。OpenAPI/Swaggerのような本格的なドキュメントは、外部開発者向けにAPIを公開する段階で検討しましょう。
Q7. APIのレート制限は個人開発でも必要ですか?
初期段階では不要ですが、公開後は検討すべきです。Vercelにデプロイしている場合、ある程度のレート制限は自動的に適用されます。ユーザー数が増えてきた段階で、Upstashなどのサービスを使ってレート制限を追加するのが現実的です。特にAIを使ったAPI(外部APIの呼び出しを含むもの)は、コスト管理の観点からも早めにレート制限を導入しましょう。
まとめ
この記事では、個人開発に特化したAPI設計の基礎から実践までを解説しました。最後にポイントを振り返りましょう。
- REST APIを選ぶ:個人開発では学習コストが低く、AIとの相性も良いREST APIが最適
- 6つの基本原則を守る:リソース指向のURL、適切なHTTPメソッド、ステータスコード、統一レスポンス、ページネーション、バージョニング
- 過剰設計を避ける:チーム開発の常識をそのまま持ち込まず、個人開発に必要な範囲で設計する
- 共通ヘルパーを作る:レスポンス形式とエラーハンドリングを統一する
- 認証・認可は絶対に省略しない:Supabase AuthとRLSを活用する
- Zodでバリデーション:入力チェックとTypeScript型定義を一元管理する
- AI活用で効率化:Claude Codeで設計を自動生成し、人間がレビューするワークフローを確立する
- CLAUDE.mdにルールを記載:API設計の一貫性を保つ最も効果的な方法
API設計は「最初の2〜3時間の投資」で、開発後半の手戻りを60%以上削減できるハイリターンな工程です。この記事で紹介したパターンをベースに、自分のプロジェクトに合わせてカスタマイズしてみてください。
ShiftBでは、このようなAPI設計・データベース設計・認証設計を含むWebサービス開発の全工程を、実践形式で学べるカリキュラムを提供しています。バイブコーディングの力を活かしつつ、プロレベルの設計スキルを身につけたい方は、まずは無料相談会でご相談ください。