Next.js
Next.js Route Handler에서 HTTP 메서드 구분하기
Route Handler에서 GET, POST, DELETE를 같은 파일에서 처리할 때, 실수 없이 하는 방법.
Next.js의 Route Handler는 page.tsx처럼 직관적으로 route.ts 파일 하나에 모든 HTTP 메서드를 정의한다. 그런데 메서드를 제대로 구분하지 않으면 요청이 잘못된 핸들러로 들어갈 수 있다.
기본 패턴
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
return NextResponse.json({ items: [] });
}
export async function POST(request: NextRequest) {
const body = await request.json();
// 항목 생성
return NextResponse.json({ id: 1 }, { status: 201 });
}
export async function PUT(request: NextRequest) {
// 항목 수정
return NextResponse.json({ updated: true });
}
export async function DELETE(request: NextRequest) {
// 항목 삭제
return NextResponse.json({ deleted: true });
}
Next.js는 HTTP 메서드 이름에 맞춰 자동으로 올바른 함수를 호출한다.
주의할 점
1. 함수 이름은 정확해야 한다
// 나쁜 예: 함수 이름이 소문자
export async function get(request: NextRequest) { }
// → 작동 안 함
// 좋은 예: 대문자
export async function GET(request: NextRequest) { }
// → 작동함
Next.js는 GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS 같은 정확한 메서드 이름만 인식한다.
2. Request body 읽기
// POST나 PUT에서 body를 읽을 때
export async function POST(request: NextRequest) {
const body = await request.json();
// body를 한 번만 읽을 수 있음
// 다시 읽으려고 하면 에러 발생
return NextResponse.json(body);
}
Request body는 한 번만 읽을 수 있다. 필요하면 변수에 저장해서 재사용하자.
export async function POST(request: NextRequest) {
const body = await request.json();
// body를 여러 번 사용
console.log(body.name);
console.log(body.email);
// body를 다시 읽으려고 하면? ❌
// const body2 = await request.json(); // 에러!
return NextResponse.json({ success: true });
}
라우팅 패턴
단순 엔드포인트
/api/items → route.ts (GET, POST)
동적 파라미터
/api/items/[id] → [id]/route.ts (GET, PUT, DELETE)
// app/api/items/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const id = params.id;
return NextResponse.json({ id });
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const id = params.id;
// id에 해당하는 항목 삭제
return NextResponse.json({ deleted: true });
}
에러 처리
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// 검증
if (!body.name) {
return NextResponse.json(
{ error: 'Name is required' },
{ status: 400 }
);
}
// 처리
const item = await db.items.create(body);
return NextResponse.json(item, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
인증 확인
import { auth } from '@/lib/auth';
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
// 인증 확인
const session = await auth();
if (!session) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// 권한 확인
if (session.user.id !== params.id) {
return NextResponse.json(
{ error: 'Forbidden' },
{ status: 403 }
);
}
// 삭제 처리
await db.items.delete(params.id);
return NextResponse.json({ success: true });
}
실제 예제: Todo API
// app/api/todos/route.ts
import { NextRequest, NextResponse } from 'next/server';
// GET: 모든 todo 조회
export async function GET(request: NextRequest) {
const todos = await db.todos.findMany();
return NextResponse.json(todos);
}
// POST: 새 todo 생성
export async function POST(request: NextRequest) {
const { title, description } = await request.json();
if (!title) {
return NextResponse.json(
{ error: 'Title required' },
{ status: 400 }
);
}
const todo = await db.todos.create({
title,
description,
completed: false,
});
return NextResponse.json(todo, { status: 201 });
}
// app/api/todos/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const todo = await db.todos.findUnique(params.id);
if (!todo) {
return NextResponse.json(
{ error: 'Not found' },
{ status: 404 }
);
}
return NextResponse.json(todo);
}
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const data = await request.json();
const todo = await db.todos.update(params.id, data);
return NextResponse.json(todo);
}
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
await db.todos.delete(params.id);
return NextResponse.json({ success: true });
}
디버깅 팁
잘못된 메서드가 호출되면:
// 모든 요청을 받아서 로깅
export async function OPTIONS(request: NextRequest) {
console.log('Method:', request.method);
console.log('URL:', request.url);
return new Response(null, { status: 204 });
}
또는 브라우저 DevTools의 Network 탭에서 요청을 확인하자.
정리
Route Handler에서 메서드를 구분하는 건 간단하지만, 실수하기 쉽다. 함수 이름을 정확하게, Request body는 한 번만 읽고, 에러 처리는 항상 포함하자. 규모가 커지면 middleware를 추가해서 인증이나 로깅을 중앙화하는 게 좋다.