# Next.js as a Backend

## 1. Route Handlers: Next.js's Answer to a Backend Endpoint

Next.js's App Router lets a `route.ts` file inside `app/` define an HTTP endpoint directly, alongside the same-framework pages that render UI (already covered in `frontend/ecosystem/01-nextjs_fundamentals/` and `02-nextjs_routing/`):

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

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  const user = await db.users.findById(params.id);
  if (!user) {
    return NextResponse.json({ error: { code: "NOT_FOUND" } }, { status: 404 });
  }
  return NextResponse.json(user);
}

export async function DELETE(req: NextRequest, { params }: { params: { id: string } }) {
  await db.users.delete(params.id);
  return new NextResponse(null, { status: 204 });
}
```

Each exported function name (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) maps directly to that HTTP method for the route — this mirrors `app.get()`/`app.post()` from `01-express_fundamentals/`, just expressed as named exports tied to a filesystem path instead of imperative registration calls. The `[id]` folder segment is Next.js's dynamic-route syntax (the same mechanism `02-nextjs_routing/` covers for pages), and it populates `params.id` here exactly the way `:id` populates `req.params.id` in Express.

## 2. When Next.js-as-Backend Is the Right Call

Using Next.js's own Route Handlers as your entire backend makes sense when:

- The backend's job is primarily **serving the same app's own frontend** — form submissions, simple CRUD backing the UI, auth callbacks — rather than being a general-purpose API consumed by many unrelated clients.
- You want **one deployable unit** (one repo, one build, one deploy) instead of coordinating a separate frontend and backend service.
- The backend logic is light enough that NestJS's or Express's dedicated backend structure (modules, DI, a real service layer — `02-nestjs_fundamentals/`) would be more ceremony than the app needs.

It's a weaker fit when the backend needs to be a genuinely independent, heavily-used API serving multiple different frontends/clients, needs its own independent scaling and deploy lifecycle separate from the UI, or needs the structured module/DI system a framework like NestJS provides for a large team. Many real systems pair Next.js (frontend + light backend-for-frontend routes) with a **separate** backend service for the substantial API — that's a legitimate architecture, not a compromise.

## 3. Edge Runtime vs. Node.js Runtime

Every Route Handler runs in one of two runtimes, chosen per-route:

```ts
export const runtime = "edge"; // or "nodejs" (the default)
```

| | Node.js runtime (default) | Edge runtime |
|---|---|---|
| Available APIs | Full Node.js API surface (`fs`, most npm packages, full `Buffer`) | A restricted, Web-standard-API-only subset (`fetch`, `Request`/`Response`, no `fs`, no arbitrary native Node modules) |
| Where it runs | A traditional server process/container | Distributed at the network edge, closer to the requesting user |
| Cold start | Slower | Near-instant |
| Best for | Database access via most ORMs/drivers, anything needing full Node APIs | Simple, fast, latency-sensitive logic (auth checks, redirects, lightweight transforms) that doesn't need Node-specific APIs |

Choosing `edge` for a route that needs a Node-only database driver will fail at build or runtime, not silently degrade — the restriction is real, not just a performance suggestion. Most routes doing real database work stay on the default `nodejs` runtime.

![A route.ts file exports HTTP method handlers that run either on the Node.js runtime with full Node APIs or on the Edge runtime with Web APIs only](diagrams/01-runtime-choice.svg)

## 4. Reading the Request Body and Query Params

```ts
export async function POST(req: NextRequest) {
  const body = await req.json(); // NextRequest extends the Web Request API -- body reading is async
  const url = new URL(req.url);
  const limit = url.searchParams.get("limit"); // query params via the standard URL API, not req.query
  return NextResponse.json({ received: body, limit });
}
```

Note the contrast with Express's `req.body` (already parsed and synchronously available once `express.json()` middleware has run, per `01-express_fundamentals/` section 5): `NextRequest` is built on the standard Web `Request` API, so the body is read via an explicit `await req.json()` call, and query params come from parsing `req.url` with the standard `URL`/`URLSearchParams` API rather than a framework-populated `req.query` object.

## Common Pitfalls

1. **Forgetting that route handler exports are named by HTTP method.** A function named `handler` instead of `GET`/`POST`/etc. is simply never called — Next.js looks specifically for those exact export names.
2. **Choosing `edge` runtime for a route that needs a Node-only dependency** (most traditional SQL drivers, `fs`-based logic) — fails outright rather than falling back.
3. **Treating Next.js-as-backend as automatically the right architecture for a large, independently-scaled API** — it's a good fit for backend-for-frontend needs, not a universal replacement for a dedicated backend service (section 2).
4. **Forgetting `await` on `req.json()`** — since the Web `Request` body API is asynchronous by design, unlike Express's already-parsed `req.body`.

## Quick Reference

```
app/api/.../route.ts
  export async function GET/POST/PUT/DELETE/PATCH(req, { params })

export const runtime = "nodejs" | "edge"   -- per-route runtime choice
  nodejs -- full Node API surface, default, needed for most DB drivers
  edge   -- Web-standard APIs only, near-instant cold start, no fs/native Node modules

await req.json()          -- read the body (async, Web Request API)
new URL(req.url).searchParams.get("x")  -- query params (no req.query)
NextResponse.json(obj, { status })       -- send a JSON response
```
