Route Handlers продвинуто
Динамические параметры
// app/api/posts/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const post = await db.post.findUnique({ where: { id: Number(id) } });
if (!post) {
return Response.json({ error: 'Not found' }, { status: 404 });
}
return Response.json(post);
}
Query-параметры
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = Number(searchParams.get('page') ?? 1);
const limit = Number(searchParams.get('limit') ?? 10);
const posts = await db.post.findMany({
skip: (page - 1) * limit,
take: limit,
});
return Response.json({ data: posts, page, limit });
}
Тело запроса
export async function POST(request: Request) {
const body = await request.json();
const post = await db.post.create({ data: body });
return Response.json(post, { status: 201 });
}
FormData
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get('file') as File;
const title = formData.get('title') as string;
// обработка
return Response.json({ ok: true });
}
Заголовки
export async function GET(request: Request) {
const auth = request.headers.get('authorization');
if (!auth?.startsWith('Bearer ')) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = auth.slice(7);
const user = await verifyToken(token);
return Response.json({ user });
}
Cookies
import { cookies } from 'next/headers';
export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
if (!token) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
return Response.json({ token });
}
// Установка
cookieStore.set('token', 'value', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7,
});
Валидация через Zod
import { z } from 'zod';
const CreatePostSchema = z.object({
title: z.string().min(3),
body: z.string().min(10),
published: z.boolean().optional(),
});
export async function POST(request: Request) {
const json = await request.json();
const result = CreatePostSchema.safeParse(json);
if (!result.success) {
return Response.json(
{ errors: result.error.flatten().fieldErrors },
{ status: 422 }
);
}
const post = await db.post.create({ data: result.data });
return Response.json(post, { status: 201 });
}
CORS
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
export async function OPTIONS() {
return new Response(null, { status: 204, headers: corsHeaders });
}
export async function GET() {
const data = await db.post.findMany();
return Response.json(data, { headers: corsHeaders });
}
Стриминг
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(encoder.encode(`data: ${i}\n\n`));
await new Promise((r) => setTimeout(r, 100));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
SSE для real-time.
Кеширование
export async function GET() {
const data = await db.post.findMany();
return Response.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
},
});
}
revalidate
export const revalidate = 60;
Webhooks
import crypto from 'crypto';
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('x-signature');
const expected = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(body)
.digest('hex');
if (signature !== expected) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(body);
// обработка
return Response.json({ received: true });
}
Rate limiting
const requests = new Map<string, number[]>();
export async function GET(request: Request) {
const ip = request.headers.get('x-forwarded-for') ?? 'unknown';
const now = Date.now();
const window = 60_000;
const limit = 100;
const timestamps = (requests.get(ip) ?? []).filter((t) => now - t < window);
if (timestamps.length >= limit) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
timestamps.push(now);
requests.set(ip, timestamps);
// обработка
}
В продакшене — Redis или специализированные сервисы.
Итоги
params— промисsearchParamsчерезURLResponse.jsonдля ответа- Zod для валидации
- CORS через заголовки
- SSE для real-time
- Webhooks с проверкой подписи