Skip to content

Route Handlers

Route Handlers are the App Router's way of building backend API endpoints, defined in files named route.ts. This lesson covers creating handlers for different HTTP methods and working with the standard Request/Response Web APIs.

route.ts Files Define API Endpoints

A file named exactly route.ts inside any folder in app/ becomes an API endpoint, reachable at that folder's URL path. Unlike page.tsx, which exports one default component, route.ts exports a named function per HTTP method it supports — GET, POST, PUT, DELETE, and so on.

Route Handlers use the standard Web Request and Response objects (the same ones available in browsers and other modern JavaScript runtimes) instead of the Node.js-specific req/res objects used by Pages Router API Routes.

// app/api/posts/route.ts
export async function GET() {
  const posts = await getPosts();
  return Response.json(posts);
}

export async function POST(request: Request) {
  const body = await request.json();
  const newPost = await createPost(body);
  return Response.json(newPost, { status: 201 });
}

GET /api/posts returns the post list; POST /api/posts creates a new one — both live in the same file, as separate exported functions.

Supported Method Exports

export async function GET(request: Request) { /* ... */ }
export async function POST(request: Request) { /* ... */ }
export async function PUT(request: Request) { /* ... */ }
export async function PATCH(request: Request) { /* ... */ }
export async function DELETE(request: Request) { /* ... */ }
  • A route.ts file only needs to export the methods it actually supports.
  • Requesting an unsupported method automatically returns a 405 response from Next.js.
  • Response.json(data, options) is the standard way to return a JSON response.
  • A route folder cannot contain both a page.tsx and a route.ts at the same segment — pick one.

Route Handlers Cheat Sheet

Common tasks when building Route Handlers.

Task Code
Read JSON body await request.json()
Read query params new URL(request.url).searchParams
Return JSON Response.json(data)
Return an error status Response.json({ error }, { status: 400 })
Read dynamic segment GET(request, { params })

Dynamic Segments in Route Handlers

Route Handlers support the same [param] dynamic segment syntax as pages. The captured value is delivered as the second argument to your handler function, inside a params object.

// app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await getPostById(params.id);
  if (!post) {
    return Response.json({ error: "Not found" }, { status: 404 });
  }
  return Response.json(post);
}

Caching Behavior for GET Handlers

By default, GET Route Handlers with no dynamic behavior can be cached similarly to statically rendered pages. Using dynamic APIs like cookies(), reading the request's URL search params, or exporting export const dynamic = "force-dynamic" opts a handler out of caching, just like a page.

Common Mistakes

  • Naming the file Route.ts or routes.ts instead of the exact required name route.ts.
  • Trying to have both a page.tsx and a route.ts in the same folder for the same path.
  • Using Node.js-style req.body instead of the Web-standard await request.json().
  • Forgetting to return a Response object — returning plain data instead causes a build error.

Key Takeaways

  • route.ts files define API endpoints in the App Router, one named export per HTTP method.
  • They use the standard Web Request/Response objects, not Node.js-style req/res.
  • Dynamic segments work the same as pages, with params passed as the handler's second argument.
  • A folder can have a page.tsx or a route.ts, but not both, for the same path.
  • Route Handlers can be cached like static content unless they use dynamic, request-specific APIs.

Pro Tip

Reach for a Route Handler specifically when you need an endpoint consumed by something other than your own Server Components — a public API, a webhook receiver, or a client-side fetch call — since Server Components can usually just call your data layer directly without needing an HTTP round-trip at all.