Next.js 15 Fullstack (App Router + Server Actions) · Leçon

Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web

Implémentez des gestionnaires GET/POST/PATCH/DELETE à l’aide des objets natifs Request et Response et de segments dynamiques.

Leçon 1 sur 413 étapes

Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web est une leçon Next.js 15 Fullstack (App Router + Server Actions) gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Next.js 15 Fullstack (App Router + Server Actions), et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

What Route Handlers Are

In the Next.js 15 App Router, a Route Handler is a file named route.ts inside the app directory. It lets you build a REST-style API endpoint without a separate Express server.

  • You export an async function named after the HTTP method: GET, POST, PATCH, DELETE, PUT, HEAD, OPTIONS.
  • Each function receives a standard Web Request and returns a standard Web Response.
  • The URL is derived from the folder path: app/api/users/route.ts serves /api/users.

Because these are built on the platform's native Fetch API, the same mental model works on Node and Edge runtimes.

// app/api/users/route.ts
export async function GET(request: Request): Promise<Response> {
  return Response.json({ users: ["Ada", "Linus"] });
}

export async function POST(request: Request): Promise<Response> {
  const body = await request.json();
  return Response.json({ created: body }, { status: 201 });
}

Returning Responses

A handler must return a Response. Next.js gives you the native object plus a convenience helper.

  • Response.json(data, init) serializes data and sets Content-Type: application/json automatically.
  • Use the second init argument to set status and custom headers.
  • For plain text or other payloads, construct new Response(body, init) directly.

Picking the right status code is part of RESTful design: 200 for reads, 201 for creates, 204 for deletes with no body.

// Three idiomatic ways to respond
Response.json({ ok: true });                       // 200 + JSON
Response.json({ id: 1 }, { status: 201 });          // 201 Created
new Response(null, { status: 204 });                // 204 No Content
new Response("pong", {
  status: 200,
  headers: { "Content-Type": "text/plain" },
});

Reading the Request Body

The incoming Request is the same object you know from fetch on the client. Its body is a stream you consume once.

  • await request.json() parses a JSON payload.
  • await request.text() reads raw text.
  • await request.formData() reads multipart or URL-encoded form submissions.

You can only read the body once. If JSON parsing can fail (malformed input), wrap it in try/catch and return 400 Bad Request.

// app/api/posts/route.ts
export async function POST(request: Request) {
  let body: { title?: string };
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: "Invalid JSON" }, { status: 400 });
  }
  if (!body.title) {
    return Response.json({ error: "title is required" }, { status: 422 });
  }
  return Response.json({ id: 1, title: body.title }, { status: 201 });
}

Reading Query Parameters

For GET requests, filters and pagination usually arrive as query-string parameters. Parse them from the request URL.

  • new URL(request.url) gives you a URL object.
  • Its searchParams is a URLSearchParams instance with get, getAll, and has.
  • Convert numeric params explicitly — every value comes in as a string.

Next.js also exposes nextUrl via NextRequest, but the native URL approach keeps your handler runtime-agnostic.

// app/api/products/route.ts  ->  /api/products?page=2&q=phone
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = Number(searchParams.get("page") ?? "1");
  const q = searchParams.get("q") ?? "";
  return Response.json({ page, q });
}

Dynamic Segments and Async Params

To handle a single resource by id, create a dynamic folder like app/api/users/[id]/route.ts. The segment is passed as the second argument.

Important Next.js 15 change: the params object is now a Promise. You must await it before reading values.

  • Type the context as { params: Promise<{ id: string }> }.
  • const { id } = await params; unwraps the segment.
  • Segment values are always strings, so parse numbers yourself.
// app/api/users/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = { id, name: "Ada" };
  return Response.json(user);
}

A Full GET-by-id with 404

RESTful reads should return the resource on success and a proper 404 Not Found when it does not exist. Never return 200 with an empty body for a missing record.

  • Look up the resource using the awaited id.
  • If nothing is found, return Response.json({ error }, { status: 404 }).
  • Otherwise return the resource with the default 200.

This handler is the canonical shape for /api/<resource>/[id].

// app/api/users/[id]/route.ts
const DB = new Map([["1", { id: "1", name: "Ada" }]]);

export async function GET(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const user = DB.get(id);
  if (!user) {
    return Response.json({ error: "User not found" }, { status: 404 });
  }
  return Response.json(user);
}

PATCH for Partial Updates

PATCH updates part of a resource, while PUT replaces it entirely. For most CRUD APIs you want PATCH: the client sends only the fields that change.

  • Read the dynamic id from the awaited params.
  • Parse the JSON body for the changed fields.
  • Merge the changes onto the existing record and return the updated resource with 200.

Return 404 if the target does not exist, and validate before merging.

// app/api/users/[id]/route.ts
export async function PATCH(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const existing = DB.get(id);
  if (!existing) {
    return Response.json({ error: "Not found" }, { status: 404 });
  }
  const changes = await request.json();
  const updated = { ...existing, ...changes, id };
  DB.set(id, updated);
  return Response.json(updated);
}

DELETE and 204 No Content

A successful DELETE typically returns 204 No Content with an empty body, signalling the resource is gone and there is nothing to send back.

  • Confirm the resource exists; if not, return 404.
  • Remove it from your store.
  • Return new Response(null, { status: 204 }) — do not call Response.json, since a 204 must have no body.

Some teams prefer 200 with the deleted object; both are valid, but be consistent across your API.

// app/api/users/[id]/route.ts
export async function DELETE(
  request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  if (!DB.has(id)) {
    return Response.json({ error: "Not found" }, { status: 404 });
  }
  DB.delete(id);
  return new Response(null, { status: 204 });
}

Reading and Setting Headers

Headers carry auth tokens, content negotiation, and caching hints. The native Request.headers and Response init both use the standard Headers API.

  • request.headers.get("authorization") reads an incoming header (case-insensitive).
  • Set response headers via the init.headers object or a Headers instance.
  • Common ones: Cache-Control, Location (for created resources), and WWW-Authenticate.

Returning 401 Unauthorized early keeps protected handlers clean.

// app/api/secret/route.ts
export async function GET(request: Request) {
  const auth = request.headers.get("authorization");
  if (auth !== "Bearer secret-token") {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  return Response.json(
    { data: "top secret" },
    { headers: { "Cache-Control": "no-store" } }
  );
}

Caching and the Runtime

In Next.js 15, GET Route Handlers are not cached by default (this changed from Next.js 14). You opt into static caching explicitly.

  • Force caching with export const dynamic = 'force-static'.
  • Set a revalidation window with export const revalidate = 60 (seconds).
  • Reading the request body, headers, or cookies makes a handler dynamic automatically.

Choose the runtime with export const runtime = 'edge' for low-latency global execution, or the default 'nodejs' when you need Node APIs.

// app/api/quote/route.ts
export const runtime = "edge";
export const revalidate = 60; // re-generate at most once per minute

export async function GET() {
  return Response.json({ quote: "Stay curious", at: Date.now() });
}

A Pure Request Router You Can Run

Route Handlers are thin wrappers over Web Request/Response. To prove the model is just standard JavaScript, here is a tiny self-contained router that dispatches by method and parses an id from the path — no framework required.

  • It builds a real Request, inspects method and url, and returns a Response.
  • The same logic you would put inside GET/POST lives here.
  • This runs in any modern runtime with the Fetch API available.
async function handle(req: Request): Promise<Response> {
  const { pathname } = new URL(req.url);
  const id = pathname.split("/").pop();
  if (req.method === "GET") {
    return Response.json({ id, name: "Ada" });
  }
  if (req.method === "DELETE") {
    return new Response(null, { status: 204 });
  }
  return Response.json({ error: "Method Not Allowed" }, { status: 405 });
}

async function main() {
  const get = await handle(new Request("http://x/api/users/1"));
  console.log(get.status, await get.json());
  const del = await handle(
    new Request("http://x/api/users/1", { method: "DELETE" })
  );
  console.log(del.status); // 204
}
main();

Quick Check

You are writing app/api/users/[id]/route.ts in Next.js 15. How do you correctly read the id segment inside the GET handler?

Recap

You now know how to design RESTful Route Handlers on the native Web Request/Response API in Next.js 15.

  • Export method-named async functions (GET, POST, PATCH, DELETE) from route.ts.
  • Read input with request.json(), request.formData(), and new URL(request.url).searchParams.
  • Access dynamic segments via const { id } = await params — params is a Promise in v15.
  • Respond with Response.json(data, { status }); use 201 for creates, 404 for missing resources, and 204 (empty body) for deletes.
  • Remember GET is uncached by default; opt in with dynamic/revalidate, and pick runtime as needed.

These patterns give you clean, predictable, framework-agnostic API endpoints.

Gratuit pour commencer

Apprends TypeScript avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
22
Leçons
88

Questions Fréquemment Posées

La leçon « Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web » est-elle gratuite ?

Oui — le texte complet de « Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Next.js 15 Fullstack (App Router + Server Actions), passe à CoddyKit PRO. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web » ?

Implémentez des gestionnaires GET/POST/PATCH/DELETE à l’aide des objets natifs Request et Response et de segments dynamiques. Tu pratiques Next.js 15 Fullstack (App Router + Server Actions) avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Next.js 15 Fullstack (App Router + Server Actions) ?

Aucune expérience préalable n'est requise. Next.js 15 Fullstack (App Router + Server Actions) sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.

Combien de temps prend la leçon « Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Next.js 15 Fullstack (App Router + Server Actions) ?

Oui. Chaque leçon Next.js 15 Fullstack (App Router + Server Actions) inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Concevoir des gestionnaires de routes RESTful avec l’API de requêtes Web
  2. Compromis entre l’environnement d’exécution Node et Edge
  3. Réponses en flux et ReadableStream dans les gestionnaires
  4. Validation des requêtes et réponses JSON typées avec Zod
← Retour à Next.js 15 Fullstack (App Router + Server Actions)