Next.js
Next.js Route Handler에서 JSON 에러 응답 통일하기
API 에러 응답 형식이 제각각이면 클라이언트에서 처리하기 힘들다. 한 가지 패턴으로 통일하자.
Next.js Route Handler를 여러 개 만들다 보면 에러 응답이 제각각이 되기 쉽다. 어떤 건 { error: "..." }, 어떤 건 { message: "..." }, 어떤 건 HTTP 상태만 반환한다.
에러 응답 형식 정하기
모든 에러 응답을 같은 형식으로 통일하면 클라이언트에서 처리하기 쉬워진다.
type ErrorResponse = {
success: false;
error: {
code: string;
message: string;
details?: unknown;
};
};
type SuccessResponse<T> = {
success: true;
data: T;
};
에러 응답 헬퍼 함수 만들기
Route Handler에서 반복되는 코드를 피하려면 헬퍼를 만든다.
// lib/api-response.ts
export function errorResponse(
code: string,
message: string,
status: number = 400,
details?: unknown
) {
return Response.json(
{
success: false,
error: { code, message, details },
},
{ status }
);
}
export function successResponse<T>(data: T, status: number = 200) {
return Response.json(
{ success: true, data },
{ status }
);
}
Route Handler에서 사용하기
// app/api/items/[id]/route.ts
import { successResponse, errorResponse } from '@/lib/api-response';
export async function GET(request: Request, { params }: Props) {
const id = params.id;
if (!id || typeof id !== 'string') {
return errorResponse(
'INVALID_ID',
'Item ID is required and must be a string',
400
);
}
try {
const item = await db.items.findUnique({ where: { id } });
if (!item) {
return errorResponse(
'NOT_FOUND',
`Item with ID ${id} not found`,
404
);
}
return successResponse(item);
} catch (error) {
console.error('Error fetching item:', error);
return errorResponse(
'INTERNAL_ERROR',
'Failed to fetch item',
500,
process.env.NODE_ENV === 'development' ? error : undefined
);
}
}
검증 에러도 통일하기
입력값 검증도 같은 형식으로 반환한다.
export async function POST(request: Request) {
try {
const body = await request.json();
// 검증
if (!body.name || typeof body.name !== 'string') {
return errorResponse(
'VALIDATION_ERROR',
'Name is required and must be a string',
400,
{ field: 'name' }
);
}
if (!body.email || !isValidEmail(body.email)) {
return errorResponse(
'VALIDATION_ERROR',
'Email must be valid',
400,
{ field: 'email' }
);
}
const item = await db.items.create({ data: body });
return successResponse(item, 201);
} catch (error) {
return errorResponse('PARSE_ERROR', 'Invalid JSON', 400);
}
}
클라이언트에서 처리하기
에러 형식이 통일되면 클라이언트 코드도 간단해진다.
const response = await fetch(`/api/items/${id}`);
const json = await response.json();
if (!json.success) {
// 모든 에러가 같은 구조
console.error(`Error: ${json.error.code} - ${json.error.message}`);
if (json.error.code === 'NOT_FOUND') {
showNotFoundPage();
} else if (json.error.code === 'VALIDATION_ERROR') {
showValidationErrors(json.error.details);
} else {
showGenericError(json.error.message);
}
} else {
// 성공 시에도 일관된 형식
processItem(json.data);
}
API 응답 형식을 처음부터 통일하면 나중에 추가하는 Route Handler도 일관성 있게 작성할 수 있다.