Skip to content

API Routes

"API Routes" is the name for backend endpoints in the Pages Router (pages/api), and it's a term still used broadly to describe any server endpoint built directly inside a Next.js app. This lesson covers the original pages/api convention and how it relates to the App Router's Route Handlers.

API Routes in the Pages Router

Any file inside pages/api/ automatically becomes a serverless API endpoint rather than a page. Instead of returning JSX, the default export is a handler function that receives Node.js-style req and res objects, similar to an Express route handler.

This was, for years, the standard way to add backend logic to a Next.js app — form submissions, webhook receivers, and simple CRUD endpoints — without needing a separate backend service.

// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from "next";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ message: "Hello from the API!" });
}

This file automatically becomes reachable at /api/hello, with no additional routing configuration.

Handler Function Signature

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === "POST") {
    // handle POST
  }
  res.status(200).json({ ok: true });
}
  • The handler receives Express-like req (request) and res (response) objects.
  • You branch on req.method manually to support multiple HTTP methods in one file.
  • res.status(code).json(data) sends a JSON response with the given status code.
  • Files under pages/api/ never render UI — they are purely backend endpoints.

API Routes (Pages Router) Cheat Sheet

Common request/response patterns for pages/api handlers.

Task Code
Read query params req.query.id
Read JSON body req.body (auto-parsed for JSON)
Send JSON response res.status(200).json(data)
Send an error res.status(400).json({ error: "..." })
Check HTTP method if (req.method === "POST")

Dynamic API Routes

Just like pages, API routes support dynamic segments using the same bracket syntax, with the captured value available on req.query.

// pages/api/posts/[id].ts
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const { id } = req.query;
  res.status(200).json({ postId: id });
}

API Routes vs. App Router Route Handlers

The App Router replaces this pattern with Route Handlers (route.ts files, covered in the next lesson), which use the standard Web Request/Response objects instead of Node.js-style req/res. Both accomplish the same goal — the API you use just depends on which router your project (or specific route) is using.

Aspect API Routes (pages/api) Route Handlers (app/.../route.ts)
Request/response style Node.js-style req/res Web-standard Request/Response
File location pages/api/*.ts app/**/route.ts
Method handling Branch on req.method Separate exported functions per method

Common Mistakes

  • Forgetting to check req.method, causing a GET-only handler to also (incorrectly) accept POST requests.
  • Returning data without calling res.status() explicitly, relying on an implicit default.
  • Mixing pages/api conventions into an App Router route.ts file (or vice versa) — the request/response APIs are different.
  • Not validating req.body, trusting client-sent data without any checks.

Key Takeaways

  • pages/api/*.ts files become serverless backend endpoints in the Pages Router.
  • Handlers use Node.js-style req/res objects, similar to Express.
  • Dynamic API routes use the same [param] bracket syntax as pages.
  • The App Router's equivalent concept is Route Handlers, using Web-standard Request/Response objects.
  • Both approaches let you build backend logic without a separate server.

Pro Tip

If you're starting a new project on the App Router, use Route Handlers instead of pages/api — but if you're maintaining an existing Pages Router codebase, there's no need to migrate working API routes just for the sake of it; both are fully supported.