API Route no Next.js com TypeScript 2026
Route Handlers do App Router são poderosos, mas tipar corretamente request, response e params dinâmicos exige atenção. Veja como fazer do jeito certo.
Por que isso é importante
API Route no Next.js com TypeScript 2026. Route Handlers do App Router são poderosos, mas tipar corretamente request, response e params dinâmicos exige atenção. Veja como fazer do jeito certo.
Route Handlers vs API Routes: o que mudou
No Pages Router, APIs ficavam em pages/api/ e usavam NextApiRequest/NextApiResponse. No App Router, as APIs usam Route Handlers em arquivos route.ts e trabalham com NextRequest/NextResponse da Web API. A abordagem é diferente e os tipos também.
Galera, a grande vantagem é que Route Handlers seguem o padrão Web standard. Se você conhece fetch() e Response, já tá em casa. A tipagem com TypeScript deixa tudo mais previsível.
Primeiro Route Handler: GET tipado
Pra criar uma API no App Router, crie um arquivo route.ts dentro de app/api/. Cada função exportada (GET, POST, PUT, DELETE, PATCH) vira um endpoint. O tipo de retorno é Response ou NextResponse.
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface User {
id: string;
name: string;
email: string;
}
// GET /api/users
export async function GET(request: NextRequest) {
const users: User[] = [
{ id: '1', name: 'João', email: 'joao@email.com' },
{ id: '2', name: 'Maria', email: 'maria@email.com' },
];
return NextResponse.json(users, { status: 200 });
}
NextResponse.json() serializa o objeto e define o Content-Type automaticamente. O TypeScript infere o tipo do JSON a partir do argumento.
POST com body tipado e validação
No App Router, o body vem do request.json(), que retorna Promise
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
role: z.enum(['admin', 'user']).default('user'),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
// POST /api/users
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const result = CreateUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten().fieldErrors },
{ status: 400 }
);
}
const user: CreateUserInput = result.data;
// user tem tipo seguro aqui
// Salvar no banco...
return NextResponse.json(
{ id: 'novo-id', ...user },
{ status: 201 }
);
} catch (error) {
return NextResponse.json(
{ error: 'Erro ao processar requisição' },
{ status: 500 }
);
}
}
Params dinâmicos tipados
Rotas dinâmicas como app/api/users/[id]/route.ts recebem os params como segundo argumento. No Next.js 15+, params é uma Promise. A tipagem fica assim:
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface RouteParams {
params: Promise<{ id: string }>;
}
// GET /api/users/:id
export async function GET(
request: NextRequest,
{ params }: RouteParams
) {
const { id } = await params;
// Buscar user pelo id...
const user = { id, name: 'João', email: 'joao@email.com' };
if (!user) {
return NextResponse.json(
{ error: 'Usuário não encontrado' },
{ status: 404 }
);
}
return NextResponse.json(user);
}
// DELETE /api/users/:id
export async function DELETE(
request: NextRequest,
{ params }: RouteParams
) {
const { id } = await params;
// Deletar user...
return NextResponse.json(
{ message: `Usuário ${id} removido` },
{ status: 200 }
);
}
Pra rotas com múltiplos params, como app/api/posts/[postId]/comments/[commentId]/route.ts, a interface fica com todos os segmentos.
interface NestedRouteParams {
params: Promise<{ postId: string; commentId: string }>;
}
export async function GET(
request: NextRequest,
{ params }: NestedRouteParams
) {
const { postId, commentId } = await params;
// Ambos os params tipados como string
}
Query params e headers tipados
Dá pra extrair query params e headers do NextRequest de forma tipada. O searchParams vem do URL object, e headers do request.
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
const QuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
search: z.string().optional(),
});
export async function GET(request: NextRequest) {
// Query params
const { searchParams } = request.nextUrl;
const query = QuerySchema.parse({
page: searchParams.get('page'),
limit: searchParams.get('limit'),
search: searchParams.get('search'),
});
// query.page é number, query.limit é number
// Headers
const authHeader = request.headers.get('authorization');
const contentType = request.headers.get('content-type');
// Cookies
const token = request.cookies.get('session-token')?.value;
return NextResponse.json({
page: query.page,
limit: query.limit,
});
}
Middleware tipado no Next.js
O middleware do Next.js roda antes de cada request. Dá pra usar pra autenticação, redirecionamento e manipulação de headers. A tipagem é direta com NextRequest e NextResponse.
// middleware.ts (raiz do projeto)
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
const isApiRoute = request.nextUrl.pathname.startsWith('/api');
const isPublicRoute = ['/api/auth/login', '/api/auth/register']
.includes(request.nextUrl.pathname);
// Rotas públicas passam direto
if (isPublicRoute) {
return NextResponse.next();
}
// Rotas de API sem token retornam 401
if (isApiRoute && !token) {
return NextResponse.json(
{ error: 'Token obrigatório' },
{ status: 401 }
);
}
// Adicionar header custom
const response = NextResponse.next();
response.headers.set('x-request-id', crypto.randomUUID());
return response;
}
export const config = {
matcher: ['/api/:path*'],
};
Padrão de resposta consistente
Crie um helper de resposta pra manter o padrão em todas as rotas. Isso facilita manutenção e garante que os clients sempre recebam a mesma estrutura.
// lib/api-response.ts
import { NextResponse } from 'next/server';
interface ApiResponse<T> {
data?: T;
error?: string;
errors?: Record<string, string[]>;
}
export function successResponse<T>(data: T, status = 200) {
return NextResponse.json<ApiResponse<T>>(
{ data },
{ status }
);
}
export function errorResponse(error: string, status = 400) {
return NextResponse.json<ApiResponse<never>>(
{ error },
{ status }
);
}
export function validationError(errors: Record<string, string[]>) {
return NextResponse.json<ApiResponse<never>>(
{ error: 'Dados inválidos', errors },
{ status: 422 }
);
}
// Uso no Route Handler
import { successResponse, errorResponse } from '@/lib/api-response';
export async function GET() {
const users = await getUsers();
return successResponse(users);
}
export async function POST(request: NextRequest) {
const result = CreateUserSchema.safeParse(await request.json());
if (!result.success) {
return validationError(result.error.flatten().fieldErrors);
}
const user = await createUser(result.data);
return successResponse(user, 201);
}
Erros comuns em Route Handlers
Atenção
Erro 1: Esquecer o await nos params. No Next.js 15+, params é Promise. Sem await, você recebe o objeto Promise em vez dos valores.
Erro 2: Não tratar body inválido no POST. Se o client manda JSON malformado, request.json() lança exceção. Sempre envolva em try/catch.
Erro 3: Usar NextApiRequest/NextApiResponse no App Router. Esses tipos são do Pages Router. No App Router, use NextRequest/NextResponse.
Erro 4: Retornar objeto direto em vez de NextResponse.json(). Route Handlers precisam retornar um Response object. Retornar um objeto puro gera erro.
Passo a passo: API completa no App Router
- Crie a pasta app/api/ com subpastas por recurso (users, posts, etc.)
- Crie route.ts em cada pasta com funções GET, POST, PUT, DELETE
- Defina interfaces de response e use NextResponse.json() tipado
- Crie schemas Zod para validar body e query params
- Tipe params dinâmicos com Promise<{ id: string }>
- Crie helpers de resposta em lib/api-response.ts
- Configure middleware.ts para autenticação global
Checklist: Route Handlers tipados
Checklist: API Next.js + TypeScript
- Route Handlers usando NextRequest e NextResponse
- Params dinâmicos tipados com Promise (Next.js 15+)
- Body validado com Zod em rotas POST/PUT/PATCH
- Query params validados com z.coerce para conversão
- Helper de resposta padronizado para toda API
- Middleware tipado para autenticação
- Tratamento de erros com try/catch em todas as rotas
- Tipos de request e response documentados
TypeScript Profissional: Projeto Completo
No CrazyStack você constrói um projeto completo com TypeScript
Node.js e React. APIs tipadas no Next.js
validação com Zod e deploy profissional. Acesse:
/comprar?src=blog-como-criar-api-nextjs-typescript
]
}
]
}
]
}