//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipifique os route handlers do App Router do Next.js com NextRequest, NextResponse e parâmetros de rota tipados. Construa endpoints de API type-safe com contratos de requisição/resposta adequados.
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
type User = {
id: string;
name: string;
email: string;
};
type CreateUserBody = {
name: string;
email: string;
};
type ApiErrorResponse = {
error: string;
};
// Handler GET
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const role = searchParams.get("role");
const users: User[] = await fetchUsersFromDb(role);
return NextResponse.json(users);
}
// Handler POST com corpo tipado
export async function POST(request: NextRequest) {
const body: unknown = await request.json();
if (!isValidCreateUserBody(body)) {
return NextResponse.json(
{ error: "Corpo da requisição inválido" } satisfies ApiErrorResponse,
{ status: 400 }
);
}
const newUser: User = {
id: crypto.randomUUID(),
name: body.name,
email: body.email,
};
// ... salvar no banco de dados
return NextResponse.json(newUser, { status: 201 });
}
// Type guard para o corpo da requisição
function isValidCreateUserBody(body: unknown): body is CreateUserBody {
return (
typeof body === "object" &&
body !== null &&
"name" in body &&
"email" in body &&
typeof (body as CreateUserBody).name === "string" &&
typeof (body as CreateUserBody).email === "string"
);
}// app/api/users/[id]/route.ts - Parâmetros de rota dinâmicos
import { NextRequest, NextResponse } from "next/server";
type RouteContext = {
params: Promise<{ id: string }>;
};
export async function GET(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
const user = await findUserById(id);
if (!user) {
return NextResponse.json({ error: "Usuário não encontrado" }, { status: 404 });
}
return NextResponse.json(user);
}
export async function DELETE(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
await deleteUserById(id);
return new NextResponse(null, { status: 204 });
}GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.NextRequest estende a API Web Request com métodos de conveniência como nextUrl.searchParams para acesso a parâmetros de query.NextResponse.json(data, init?) cria uma resposta JSON com cabeçalhos Content-Type adequados. O parâmetro data aceita qualquer valor serializável.params da rota são envolvidos em uma Promise e devem ser aguardados (await). Isso se alinha com o padrão de params assíncronos usado em componentes de página.Validação Zod em route handlers:
import { z } from "zod";
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
});
export async function POST(request: NextRequest) {
const body = await request.json();
const parsed = CreateUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: "Falha na validação", details: parsed.error.flatten() },
{ status: 400 }
);
}
const { name, email } = parsed.data; // Totalmente tipado
// ... criar usuário
}Configurando cabeçalhos e cookies:
export async function GET(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token) {
return NextResponse.json({ error: "Não autorizado" }, { status: 401 });
}
const response = NextResponse.json({ data: "protegido" });
response.headers.set("X-Custom-Header", "valor");
return response;
}Resposta em streaming:
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (const chunk of ["Olá", " ", "Mundo"]) {
controller.enqueue(encoder.encode(chunk));
await new Promise((r) => setTimeout(r, 100));
}
controller.close();
},
});
return new NextResponse(stream, {
headers: { "Content-Type": "text/plain" },
});
}request.json() retorna Promise<any>. Sempre valide ou converta o resultado antes de usá-lo.NextResponse.json() aceita qualquer valor. Use satisfies para verificar a forma: NextResponse.json({ error: "msg" } satisfies ApiErrorResponse)./api/users/123 terão id como string. Analise-os explicitamente: parseInt(id, 10).params é uma Promise. Esquecer de await resulta em um objeto Promise em vez dos params. Este é um problema comum de migração do Next.js 14.request.json() lança um erro se o corpo não for JSON válido. Envolva-o em um try/catch para robustez.new Response() em vez de NextResponse funciona, mas perde recursos específicos do Next.js como os helpers cookies() e headers().| Abordagem | Prós | Contras |
|---|---|---|
| Route Handlers (App Router) | APIs padrão Web, suporte a streaming | Validação manual necessária |
| Server Actions | Integração mais estreita com formulários React | Não para consumidores de API de terceiros |
| API Routes (Pages Router) | Padrão req/res familiar | Legado, NextApiRequest é específico do Node |
| tRPC | Segurança de tipo ponta a ponta | Dependência adicional |
| API Externa (Express, Fastify) | Controle total, implantação independente | Serviço separado para manter |
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONSroute.ts.export async function GET(request: NextRequest) {
const role = request.nextUrl.searchParams.get("role");
// role é string | null
}Request.json() não tem como conhecer a forma do seu payload em tempo de compilação.type RouteContext = {
params: Promise<{ id: string }>;
};
export async function GET(request: NextRequest, context: RouteContext) {
const { id } = await context.params;
}Promise e devem ser aguardados.Promise em vez dos params reais.return NextResponse.json({ error: "msg" } satisfies ApiErrorResponse);safeParse e verifique parsed.success:const parsed = CreateUserSchema.safeParse(await request.json());
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const { name, email } = parsed.data; // Totalmente tipado/api/users/123 retorna id como "123" (string).parseInt(id, 10) ou Number(id) se precisar de um número.request.json() lança um erro em tempo de execução.new Response() funciona, mas carece de helpers específicos do Next.js como cookies() e headers().NextResponse estende Response com métodos de conveniência. Prefira-o para recursos do Next.js.function isValidBody(body: unknown): body is CreateUserBody {
return (
typeof body === "object" &&
body !== null &&
"name" in body &&
typeof (body as CreateUserBody).name === "string"
);
}Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥