개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

Next.js API Routes — App Router에서 API 엔드포인트 만들기

2025년 5월 21일
Next.jsAPI RoutesRoute HandlersGETPOST

Next.js App Router는 Pages Router의 pages/api/ 방식을 대체하는 Route Handlers를 도입했다. route.ts 파일 하나로 HTTP 메서드별 핸들러를 선언적으로 정의할 수 있으며, Web 표준 Request/Response API를 기반으로 동작한다. 기존 Express 스타일과는 달리 함수 이름이 곧 HTTP 메서드가 되는 구조라 직관적이다.

파일 구조

app/
└── api/
    ├── users/
    │   ├── route.ts          ← GET /api/users, POST /api/users
    │   └── [id]/
    │       └── route.ts      ← GET /api/users/:id, PUT, DELETE
    └── posts/
        └── route.ts

page.tsx와 같은 디렉터리에 route.ts를 둘 수 없다. 라우트 세그먼트는 페이지 또는 핸들러 중 하나만 가진다.

GET / POST 핸들러

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const { searchParams } = request.nextUrl
  const page = Number(searchParams.get('page') ?? 1)

  const users = await db.user.findMany({ skip: (page - 1) * 10, take: 10 })

  return NextResponse.json({ users, page })
}

export async function POST(request: NextRequest) {
  const body = await request.json()

  if (!body.email) {
    return NextResponse.json({ error: 'email required' }, { status: 400 })
  }

  const user = await db.user.create({ data: body })
  return NextResponse.json(user, { status: 201 })
}

NextRequest — Web 표준 Request를 확장한 Next.js 클래스. nextUrl, cookies, geo 등의 편의 속성을 추가로 제공한다.

동적 라우트 API

URL 세그먼트 값은 두 번째 인자 context의 params로 전달된다.

// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'

type Context = { params: Promise<{ id: string }> }

export async function GET(_req: NextRequest, { params }: Context) {
  const { id } = await params
  const user = await db.user.findUnique({ where: { id } })

  if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 })
  return NextResponse.json(user)
}

export async function DELETE(_req: NextRequest, { params }: Context) {
  const { id } = await params
  await db.user.delete({ where: { id } })
  return new NextResponse(null, { status: 204 })
}

Next.js 15부터 params는 비동기(Promise) 타입으로 변경되었다. await params로 구조 분해해야 한다.

응답 형식 비교

상황반환 방법
JSON 응답NextResponse.json(data, { status })
빈 응답 (204)new NextResponse(null, { status: 204 })
리다이렉트NextResponse.redirect(new URL('/login', req.url))
스트리밍new Response(stream, { headers })

캐싱 동작

GET 핸들러는 기본적으로 정적으로 캐싱된다. request 객체를 사용하거나 dynamic 옵션을 설정하면 동적 모드로 전환된다.

// 항상 동적으로 실행
export const dynamic = 'force-dynamic'

export async function GET() {
  const data = await fetch('https://api.example.com/live')
  return NextResponse.json(await data.json())
}

Route Handlers는 Middleware와 조합해 인증 로직을 엣지에서 처리하거나, next/headers의 cookies·headers로 요청 컨텍스트에 접근하는 패턴으로 확장할 수 있다.

← 이전 글Next.js Server Actions — 서버 함수를 직접 호출하는 방법
다음 글 →Next.js SSG — 빌드 시 HTML을 미리 생성하는 방법