In the Next.js App Router, how do you define an HTTP endpoint in a route.ts file, and what happens when a request arrives with a method the file does not export?
answer
- one file, one URL segment
- named exports, not a default
- export name is the method name
- unexported method answers 405
- cannot share a folder with page.tsx
basics
~20 sA route.ts file turns its folder's path into an HTTP endpoint. You export one async function per HTTP method, named exactly GET, POST, PUT, PATCH, DELETE, HEAD or OPTIONS, and Next.js answers 405 Method Not Allowed for any method you did not export.
solid answer
~50 sIn the App Router a file named `route.ts` (or `route.js`) inside `app/` becomes the endpoint for that folder's URL — `app/api/orders/route.ts` serves `/api/orders`. Instead of one default handler that inspects `request.method`, you export a separate named async function per method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` and `OPTIONS`. Each receives the incoming request and returns a Web `Response` — commonly `Response.json(...)` or `NextResponse.json(...)`. Next matches the request method to the export, so a `DELETE` against a file that only exports `GET` and `POST` gets an automatic `405 Method Not Allowed`; the route exists, so it is not a 404, and you never write that branch yourself. Next will also implement `OPTIONS` for you when you have not exported one. Two constraints trip people up: `route.ts` and `page.tsx` cannot share a segment, and `app/api` is only a convention — a handler works at any path.
code
typescript · 15 lines// app/api/orders/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const status = request.nextUrl.searchParams.get('status') ?? 'open'
return NextResponse.json({ status, orders: [] })
}
export async function POST(request: NextRequest) {
const body = await request.json()
if (!body?.sku) {
return NextResponse.json({ error: 'sku is required' }, { status: 400 })
}
return NextResponse.json({ id: 'ord_1', sku: body.sku }, { status: 201 })
}go deeper
Be ready to name the file, route.ts, show one named async export per HTTP method, and say plainly that the export name is the method name. Expect an immediate follow-up about a method you did not export.
Explain the mechanics: Next matches the request method to the export and generates the 405 itself, handlers take a request and return a Web Response, and route.ts cannot share a segment with page.tsx.
Show judgment about the endpoint surface — which methods you actually export, how request bodies are validated, and what a consistent error response looks like across every handler in the app.
Own the conventions: where endpoints live, the shared response and error envelope, how versioning appears in the path, and how dozens of route.ts files stay consistent without each author reinventing the shape.
## What a route.ts file is In the App Router every folder under `app/` is a URL segment, and the files inside it declare what that segment does. `page.tsx` declares UI. `route.ts` (or `route.js`) declares an HTTP endpoint. `app/api/orders/route.ts` answers requests to `/api/orders`; `app/webhooks/stripe/route.ts` answers `/webhooks/stripe`. The `api` folder name carries no special meaning to the framework — it is a convention carried over from the Pages Router's `pages/api`, kept because it reads well. A route handler is not a React component. It renders nothing, no layout wraps it, no `loading` or `error` file applies to it, and none of its code ships to the browser. It takes a request and returns a response, and that is the whole contract. ## One named export per method Instead of a single default handler that branches on the method, you export one async function per HTTP method, named exactly after the method in upper case: ```ts // app/api/orders/route.ts export async function GET() { /* ... */ } export async function POST() { /* ... */ } export async function DELETE() { /* ... */ } ``` The recognised names are `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD` and `OPTIONS`. A default export is not routed, and an export under any other name is just a module-level value. This is the biggest shape difference from the Pages Router, where `pages/api/orders.ts` exported one default `(req, res)` function and you wrote the `switch (req.method)` yourself. Splitting by method has a practical payoff: each function is separately typed, separately testable, and the file tells you at a glance which verbs the endpoint supports. ## What happens to a method you did not export Next matches the incoming method to an export. With no matching export it answers `405 Method Not Allowed` itself. Note the code carefully: the *route* exists — the file matched the path — so an unsupported method is not a 404. Candidates who guess 404 are picturing a router that keys on path alone. `OPTIONS` is the one method Next will supply when you have not written it, answering with an `Allow` header derived from the exports you did write. Export your own `OPTIONS` when you need control over that response, for example to attach cross-origin headers. ## What the handler receives and returns The first argument is the incoming request, typed as `NextRequest` — a subclass of the Web `Request`, so `await request.json()`, `await request.formData()`, `request.headers` and `request.method` all behave as they do anywhere else on the platform. When the route contains a dynamic segment, as in `app/api/orders/[id]/route.ts`, a second context argument carries `params`; in Next.js 15 and 16 that `params` is a Promise you must await. The return value must be a Web `Response`. `new Response(body, init)` and the static `Response.json(value, init)` both work; `NextResponse.json(value, init)` is Next's subclass and reads a little more idiomatically inside a Next codebase. Returning a plain object, or returning nothing, is an error — there is no response object to mutate the way `res.status(200).json(...)` did in the Pages Router. ```ts export async function POST(request: Request) { const body = await request.json() return Response.json({ received: body }, { status: 201 }) } ``` ## Where route.ts may live Anywhere under `app/`, with one hard rule: a segment cannot contain both `route.ts` and `page.tsx`. Both claim the same URL and Next cannot decide whether to render UI or return raw HTTP, so the build fails with a conflict. In practice the page and the endpoint live on separate paths — `app/orders/page.tsx` for the screen, `app/api/orders/route.ts` for the endpoint. Route Segment Config exports apply to `route.ts` as they do to pages: `export const dynamic`, `export const revalidate`, `export const runtime`, `export const maxDuration`. They must be plain, statically analysable `const` exports; a computed value will not be read. ## Typical mistakes Writing a default export and wondering why every request 405s. Lower-casing an export name, since `get` is not routed. Putting `route.ts` beside `page.tsx`. Assuming the file must sit under `app/api`. And, from Pages Router habit, expecting a `(req, res)` pair — in the App Router you construct and return a `Response` instead of writing into one.
- Can a route.ts and a page.tsx live in the same folder?No. Both claim the same URL, so Next.js fails the build with a conflict. Move the endpoint to its own segment — `app/api/orders/route.ts` alongside `app/orders/page.tsx` is the usual shape. Worth adding that layouts never wrap a route handler either: it returns raw HTTP, not UI, so the layout tree simply does not apply.
- How does a route handler read a dynamic segment such as [id]?Through the handler's second argument, a context object carrying `params`. In Next.js 15 and 16 `params` is a Promise, so you await it: `export async function GET(request, { params }) { const { id } = await params }`. Earlier versions passed a plain object, which is why older snippets destructure it directly.
- Must you return NextResponse, or is a plain Response enough?A plain Web `Response` is enough. Route handlers are built on the standard `Request`/`Response` objects, so `new Response(body, init)` and `Response.json(value, init)` work fine. `NextResponse` is a subclass adding Next-specific conveniences — reach for it when you want them, not because the framework demands it.
saying these in an interview costs you the question
- Exports a default function and switches on request.method
- Believes route handlers only work under app/api
- Expects an unexported method to return 404 rather than 405
- Puts route.ts next to page.tsx in the same folder
- Thinks a layout wraps route handler output