0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · Lekcja

Projektowanie RESTful route handlerów za pomocą Web Request API

Implementuj handlery GET/POST/PATCH/DELETE za pomocą natywnych obiektów Request i Response oraz segmentów dynamicznych.

Projektowanie RESTful route handlerów za pomocą Web Request API to bezpłatna lekcja Next.js 15 Fullstack (App Router + Server Actions) na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Next.js 15 Fullstack (App Router + Server Actions), a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Next.js 15 Fullstack (App Router + Server Actions) zawiera 4 lekcji w sumie.

Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.

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.

Często zadawane pytania

Czy lekcja „Projektowanie RESTful route handlerów za pomocą Web Request API” jest bezpłatna?

Tak — pełny tekst „Projektowanie RESTful route handlerów za pomocą Web Request API” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Next.js 15 Fullstack (App Router + Server Actions), przejdź na CoddyKit PRO. Kurs Next.js 15 Fullstack (App Router + Server Actions) zawiera 4 lekcji w sumie.

Co nauczysz się w „Projektowanie RESTful route handlerów za pomocą Web Request API”?

Implementuj handlery GET/POST/PATCH/DELETE za pomocą natywnych obiektów Request i Response oraz segmentów dynamicznych. Ćwiczysz Next.js 15 Fullstack (App Router + Server Actions) z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Next.js 15 Fullstack (App Router + Server Actions)?

Nie wymagamy żadnego doświadczenia. Next.js 15 Fullstack (App Router + Server Actions) w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Projektowanie RESTful route handlerów za pomocą Web Request API”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Next.js 15 Fullstack (App Router + Server Actions)?

Tak. Każda lekcja Next.js 15 Fullstack (App Router + Server Actions) zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Projektowanie RESTful route handlerów za pomocą Web Request API
  2. Kompromisy między środowiskiem Node a Edge
  3. Strumieniowanie odpowiedzi i ReadableStream w handlerach
  4. Walidacja żądań i typowane odpowiedzi JSON z Zod
← Powrót do Next.js 15 Fullstack (App Router + Server Actions)