Route Handlers를 통한 커스텀 API 엔드포인트 생성

웹 애플리케이션을 개발하다 보면 단순히 페이지를 보여주는 것을 넘어, 외부 시스템과 통신하거나 복잡한 비즈니스 로직을 처리할 '통로'가 필요해집니다. Next.js 13 버전부터 도입된 Route Handlers는 기존의 API Routes를 계승하면서도, App Router의 강력한 기능을 등에 업고 더 유연하고 성능 지향적인 API 엔드포인트 생성을 가능하게 합니다.
단순히 데이터를 주고받는 통로를 넘어, 왜 Route Handlers가 현대 웹 생태계에서 필수적인 도구가 되었는지 깊이 있게 살펴보겠습니다.
1. Route Handlers의 본질: 클라이언트와 서버 사이의 통제소
과거에는 프론트엔드 프레임워크와 별도로 백엔드 서버(Express, NestJS 등)를 구축하는 것이 일반적이었습니다. 하지만 Next.js의 Route Handlers를 사용하면 동일한 프로젝트 내에서 완벽하게 분리된 서버 사이드 엔드포인트를 구축할 수 있습니다.
작동 원리 (Deep Dive)
Route Handlers는 웹 표준 Request와 Response API를 기반으로 동작합니다. 이는 특정 라이브러리에 종속되지 않고 브라우저 표준을 서버에서도 그대로 사용할 수 있음을 의미합니다.
- 위치 기반 라우팅: app 디렉토리 내에 route.ts (또는 .js) 파일을 생성하면, 해당 폴더 경로가 곧 API 엔드포인트 주소가 됩니다.
- HTTP 메서드 대응: GET, POST, PUT, DELETE 등 표준 메서드를 함수 이름으로 선언하여 직관적으로 요청을 처리합니다.
일상 속의 비유: 스마트 무인 택배함 사용자는 택배함의 특정 칸(엔드포인트)에 가서 물건을 넣거나(POST), 빼거나(GET), 수정(PATCH)합니다. 사용자는 내부에서 택배 기사가 어떻게 분류하고 시스템에 등록하는지 알 필요가 없습니다. Route Handlers는 바로 이 택배함처럼, 클라이언트가 복잡한 내부 로직을 몰라도 안전하게 데이터를 주고받을 수 있게 해주는 창구 역할을 합니다.
2. 실전 Hands-on: 실시간 재고 관리 시스템 구현
단순한 "Hello World" 대신, 이커머스 서비스에서 특정 상품의 재고를 확인하고 주문을 처리하는 실질적인 API 시나리오를 구성해 보겠습니다.
폴더 구조
app/api/inventory/[id]/route.ts
코드 구현
// app/api/inventory/[id]/route.ts
import { NextResponse } from 'next/server';
/**
* 특정 상품의 재고를 조회하는 GET 요청 처리
*/
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const productId = params.id;
// 실제 환경에서는 DB 조회를 수행합니다.
// 여기서는 비즈니스 로직을 시뮬레이션합니다.
const inventoryData = {
id: productId,
name: "프리미엄 기계식 키보드",
stock: 42,
status: "IN_STOCK"
};
if (!productId) {
return NextResponse.json({ error: "상품 ID가 필요합니다." }, { status: 400 });
}
return NextResponse.json(inventoryData);
}
/**
* 주문 발생 시 재고를 차감하는 POST 요청 처리
*/
export async function POST(
request: Request,
{ params }: { params: { id: string } }
) {
const productId = params.id;
// 클라이언트가 보낸 JSON 데이터 파싱
const body = await request.json();
const { orderQuantity } = body;
try {
// 1. 재고 검증 로직 (예시: 재고가 주문량보다 적은지 확인)
if (orderQuantity > 50) {
throw new Error("재고가 부족합니다.");
}
// 2. 비즈니스 로직 수행 (DB 업데이트 등)
console.log(`상품 ${productId}의 재고를 ${orderQuantity}개 차감합니다.`);
return NextResponse.json({
message: "주문이 성공적으로 접수되었습니다.",
remainingStock: 50 - orderQuantity
}, { status: 201 });
} catch (error: any) {
// 발생 가능한 오류 상황에 대한 트러블슈팅 응답
return NextResponse.json(
{ error: error.message || "서버 내부 오류가 발생했습니다." },
{ status: 500 }
);
}
}
트러블슈팅 (Troubleshooting) 팁
- 405 Method Not Allowed: route.ts에 정의되지 않은 메서드(예: 정의는 GET만 했는데 PATCH로 요청)를 보낼 때 발생합니다. 메서드 이름을 대문자로 정확히 선언했는지 확인하세요.
- JSON 파싱 에러: request.json()은 본문이 비어있을 경우 에러를 던집니다. 요청을 보내기 전에 반드시 Content-Type: application/json 헤더가 설정되었는지 확인해야 합니다.
3. 기술적 트레이드오프 (Trade-offs)
Route Handlers는 강력하지만 모든 상황에서 정답은 아닙니다.
장점
- 보안: DB API 키나 비밀번호 같은 민감한 정보를 클라이언트에 노출하지 않고 서버에서만 처리할 수 있습니다.
- CORS 제어: 외부 도메인에서 우리 API에 접근하는 것을 정밀하게 제어할 수 있습니다.
- 엣지 런타임 지원: 설정에 따라 Vercel Edge Network에서 실행하여 전 세계 사용자에게 매우 빠른 응답 속도를 제공합니다.
고려사항 및 한계
- 콜드 스타트(Cold Start): 서버리스 환경에서 구동될 경우, 오랫동안 사용하지 않다가 호출하면 첫 응답이 약간 늦어질 수 있습니다.
- 복잡성: 단순한 데이터 페칭(Fetching)은 Server Components에서 직접 수행하는 것이 더 효율적일 수 있습니다. Route Handlers는 클라이언트 사이드 인터랙션이 필요하거나 **외부 웹훅(Webhook)**을 처리할 때 사용하는 것이 적절합니다.