backend-patterns - バックエンドパターン集
バックエンドコード規約とパターン。
使い方
新しいAPIルートを作る時、このスキルを参照
- APIルート構造
認証パターン(Next.js例)
// 認証ミドルウェアを使用 import { withAuth } from '@/lib/middleware/with-auth'; import { apiSuccess, apiError } from '@/lib/api-response';
export const GET = withAuth(async ({ userId, request }) => { const data = await repository.findByUserId(userId); return apiSuccess({ data }); });
export const POST = withAuth(async ({ userId, request }) => { const body = await request.json(); // バリデーション & 処理 return apiSuccess({ created: result }); });
Express.js例
import { Router } from 'express'; import { authenticate } from '@/middleware/auth'; import { validate } from '@/middleware/validate';
const router = Router();
router.get('/', authenticate, async (req, res) => { const data = await repository.findByUserId(req.user.id); res.json({ success: true, data }); });
router.post('/', authenticate, validate(createSchema), async (req, res) => { const result = await repository.create(req.body); res.status(201).json({ success: true, data: result }); });
- レスポンス形式
統一レスポンス
// lib/api-response.ts export function apiSuccess<T>(data: T, options?: { status?: number }) { return Response.json( { success: true, data, timestamp: new Date().toISOString() }, { status: options?.status ?? 200 } ); }
export function apiError(message: string, options?: { status?: number; code?: string }) { return Response.json( { success: false, error: message, code: options?.code, timestamp: new Date().toISOString() }, { status: options?.status ?? 400 } ); }
レスポンス形式
// 成功 { "success": true, "data": { ... }, "timestamp": "2026-01-19T12:00:00.000Z" }
// エラー { "success": false, "error": "Error message", "code": "ERROR_CODE", "timestamp": "2026-01-19T12:00:00.000Z" }
- バリデーション
Zodスキーマ
import { z } from 'zod';
const createUserSchema = z.object({ email: z.string().email('Invalid email'), name: z.string().min(1, 'Name is required').max(100), role: z.enum(['user', 'admin']).optional(), });
// APIルートで使用 const validation = createUserSchema.safeParse(body); if (!validation.success) { return apiError('Invalid input', { status: 400 }); } const { email, name, role } = validation.data;
共通スキーマ
// UUID const idSchema = z.string().uuid('Invalid ID format');
// ページネーション const paginationSchema = z.object({ page: z.coerce.number().min(1).default(1), limit: z.coerce.number().min(1).max(100).default(20), });
- Repository パターン
基本構造
// lib/repositories/user.repository.ts import { db } from '@/db'; import { users } from '@/db/schema'; import { eq } from 'drizzle-orm';
export interface CreateUserInput { email: string; name: string; }
export const userRepository = { async create(input: CreateUserInput) { const [result] = await db.insert(users).values(input).returning(); return result; },
async findById(id: string) { const [result] = await db .select() .from(users) .where(eq(users.id, id)) .limit(1); return result ?? null; },
async findByEmail(email: string) { const [result] = await db .select() .from(users) .where(eq(users.email, email)) .limit(1); return result ?? null; },
async update(id: string, input: Partial<CreateUserInput>) { const [result] = await db .update(users) .set({ ...input, updatedAt: new Date() }) .where(eq(users.id, id)) .returning(); return result; },
async delete(id: string) { await db.delete(users).where(eq(users.id, id)); }, };
- エラーハンドリング
export const POST = withAuth(async ({ userId, request }) => { try { const body = await request.json(); const result = await repository.create(body); return apiSuccess({ result }); } catch (error) { console.error('[API] Error:', error);
if (error instanceof z.ZodError) {
return apiError('Validation failed', { status: 400 });
}
if (error instanceof Error && error.message.includes('unique')) {
return apiError('Already exists', { status: 409 });
}
return apiError('Internal server error', { status: 500 });
} });
- チェックリスト
新しいAPIルート作成時:
-
認証ミドルウェア適用
-
リクエストバリデーション(Zod)
-
統一レスポンス形式使用
-
エラーハンドリング(try-catch)
-
認可チェック(リソース所有者確認)
-
ログ出力
- アンチパターン
// ❌ 認証なし export async function GET(request: Request) { const data = await db.select().from(users); return Response.json(data); }
// ❌ バリデーションなし const { email } = await request.json(); await db.insert(users).values({ email });
// ❌ エラー握りつぶし try { await riskyOperation(); } catch (e) { // 何もしない }