Next.js supports the Backend for Frontend (BFF) pattern, allowing you to build public-facing endpoints that handle HTTP requests and return any content type—not just HTML. These endpoints can access external data sources, perform mutations, and serve as a lightweight API layer.
To scaffold a new project with backend capabilities:
npx create-next-app@latest --apiThis includes an example route.ts file in the app/ directory.
Route Handlers are public HTTP endpoints. You can define them using route.ts or route.js inside the app directory:
// app/api/route.ts
export function GET(request: Request) {
return new Response('Hello from backend')
}export async function POST(request: Request) {
try {
await submit(request)
return new Response(null, { status: 204 })
} catch (reason) {
const message = reason instanceof Error ? reason.message : 'Unexpected error'
return new Response(message, { status: 500 })
}
}Tip: Avoid exposing sensitive error messages to the client.
Route Handlers can serve JSON, XML, images, files, and plain text. Common endpoint conventions include:
Example: serving an RSS feed:
// app/rss.xml/route.ts
export async function GET(request: Request) {
const rssData = await fetchRSS()
const rssFeed = `<rss>...</rss>`
return new Response(rssFeed, {
headers: { 'content-type': 'application/xml' },
})
}Use request.json(), request.formData(), or request.text() to read the body:
export async function POST(request: Request) {
const data = await request.json()
return Response.json({ data })
}export async function POST(request: Request) {
const formData = await request.formData()
const email = formData.get('email')
const contents = formData.get('contents')
try {
await validateInputs({ email, contents })
const info = await sendMail({ email, contents })
return Response.json({ messageId: info.messageId })
} catch (reason) {
return new Response('Error', { status: 500 })
}
}You can only read the request body once. Clone the request if needed:
const cloned = request.clone()
await cloned.body()Route Handlers can transform and aggregate data from multiple sources:
export async function POST(request: Request) {
const body = await request.json()
const params = new URLSearchParams({ lat: body.lat, lng: body.lng })
const response = await fetch(`${weatherEndpoint}?${params}`)
const raw = await response.text()
const parsed = parseWeatherData(raw)
return new Response(parsed, { status: 200 })
}Use Route Handlers to proxy requests to external backends:
// app/api/[...slug]/route.ts
export async function POST(request: Request, { params }) {
const isValid = await isValidRequest(request.clone())
if (!isValid) return new Response(null, { status: 400 })
const pathname = params.slug.join('/')
const proxyURL = new URL(pathname, 'https://nextjs.org')
const proxyRequest = new Request(proxyURL, request)
return fetch(proxyRequest)
}Next.js extends the Web APIs with NextRequest and NextResponse:
nextUrl for parsed pathname and query paramsjson(), redirect(), and rewrite()export async function GET(request: NextRequest) {
const url = request.nextUrl
if (url.searchParams.get('redirect')) {
return NextResponse.redirect(new URL('/', request.url))
}
return NextResponse.json({ pathname: url.pathname })
}Next.js makes it easy to build a backend layer for your frontend. With Route Handlers, Proxy, and extended Web APIs, you can serve content, process data, and integrate with external systems—all within your app directory.