「TypeScript + app-router + zod」で、退勤記録(より抽象化して“エントリー”)APIの信頼性をどう担保しているかをステップで解説します。
導入と課題認識
クライアントサイドが /entry_records に user_id と recorded_at を送る場面で、無効なデータを DB に入れるのを防ぐためにリクエストバリデーションが必須。TypeScript と zod を組み合わせて型安全かつ再現性ある検証を目指す。
app-router 滞在の POST ハンドラ
route.ts に export async function POST(request: Request) を置き、await request.json() で リクエストボディ を取得。
zod スキーマでの型付きバリデーション(validator.ts):
import { z } from 'zod'
export const entrySchema = z.object({
user_id: z
.string()
.nonempty('user_id は必須です')
.regex(/^[a-zA-Z0-9]+$/, '英数字のみ許可'),
recorded_at: z
.string()
.nonempty('記録日時は必須です')
.refine((value) => !Number.isNaN(Date.parse(value)), 'ISO8601形式である必要があります'),
})
このスキーマを route.ts で entrySchema.safeParse(body) し、失敗時は 400 でエラー詳細を返す。safeParse は zod が提供するメソッドで、成否に応じて明示的に分岐できる。
POST ハンドラ実装例:
import { NextResponse } from 'next/server'
import { entrySchema } from './validator'
export async function POST(request: Request) {
try {
const payload = await request.json()
const parsed = entrySchema.safeParse(payload)
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.format() }, { status: 400 })
}
const { user_id, recorded_at } = parsed.data
// 必要なら getServerSession で認証チェック → repository へ渡して保存
return NextResponse.json({ message: '登録完了' }, { status: 201 })
} catch (error) {
console.error(error)
return NextResponse.json({ error: '予期せぬエラー' }, { status: 500 })
}
}
この構成なら、postWorkRecord(クライアント)の user_id/clock_out_time 送信との責務が分離され、API ルートは薄く保てる。
運用ポイント
.env で開発/本番 URL を切り替える checkEnvironment などのユーティリティを利用し、クライアントからのリクエストを API ルート経由でリポジトリに渡して DB 挿入。バリデーションロジックを validator.ts に集約すると再利用性が高まる。
まとめ
型/バリデーションのルールを zod で定義することで、エラーケースが明示的になる。