Проектирование обработчиков маршрутов REST с Web Request API
Реализуйте обработчики GET/POST/PATCH/DELETE с нативными объектами Request и Response и динамическими сегментами.
«Проектирование обработчиков маршрутов REST с Web Request API» — бесплатный урок Next.js 15 Fullstack (App Router + Server Actions) на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Next.js 15 Fullstack (App Router + Server Actions), и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Next.js 15 Fullstack (App Router + Server Actions) содержит 4 уроков всего.
Части этого урока еще не переведены и отображаются на английском.
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
Requestand returns a standard WebResponse. - The URL is derived from the folder path:
app/api/users/route.tsserves/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)serializesdataand setsContent-Type: application/jsonautomatically.- Use the second
initargument to setstatusand customheaders. - 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 aURLobject.- Its
searchParamsis aURLSearchParamsinstance withget,getAll, andhas. - 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
idfrom 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 callResponse.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.headersobject or aHeadersinstance. - Common ones:
Cache-Control,Location(for created resources), andWWW-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, inspectsmethodandurl, and returns aResponse. - The same logic you would put inside
GET/POSTlives 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) fromroute.ts. - Read input with
request.json(),request.formData(), andnew URL(request.url).searchParams. - Access dynamic segments via
const { id } = await params— params is a Promise in v15. - Respond with
Response.json(data, { status }); use201for creates,404for missing resources, and204(empty body) for deletes. - Remember
GETis uncached by default; opt in withdynamic/revalidate, and pickruntimeas needed.
These patterns give you clean, predictable, framework-agnostic API endpoints.
Часто задаваемые вопросы
Урок «Проектирование обработчиков маршрутов REST с Web Request API» бесплатный?
Да — полный текст урока «Проектирование обработчиков маршрутов REST с Web Request API» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Next.js 15 Fullstack (App Router + Server Actions), подпишись на CoddyKit PRO. Курс Next.js 15 Fullstack (App Router + Server Actions) содержит 4 уроков всего.
Чему я научусь в уроке «Проектирование обработчиков маршрутов REST с Web Request API»?
Реализуйте обработчики GET/POST/PATCH/DELETE с нативными объектами Request и Response и динамическими сегментами. Ты практикуешь Next.js 15 Fullstack (App Router + Server Actions) с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Next.js 15 Fullstack (App Router + Server Actions)?
Предыдущий опыт не требуется. Next.js 15 Fullstack (App Router + Server Actions) на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Проектирование обработчиков маршрутов REST с Web Request API»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Next.js 15 Fullstack (App Router + Server Actions)?
Да. Каждый урок Next.js 15 Fullstack (App Router + Server Actions) включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Проектирование обработчиков маршрутов REST с Web Request API
- Компромиссы между средами Node и Edge
- Потоковые ответы и ReadableStream в обработчиках
- Проверка запросов и типизированные ответы JSON с Zod