Building End-to-End Type-Safe REST APIs with Next.js & Zod
Learn how to validate request payloads at runtime with Zod while maintaining compile-time TypeScript guarantees.
1. The Challenge of Runtime Boundaries
TypeScript compiles down to vanilla JavaScript. At runtime in your HTTP handlers, `request.json()` returns `any` or `unknown`. If a client sends malformed data, your backend may fail with cryptic runtime errors. Zod fixes this by acting as a runtime validator and TypeScript type inferrer simultaneously.
2. Defining Your Schema
Start by defining the exact schema you want to accept. Always sanitize strings with .trim() and enforce sensible minimum/maximum length constraints.
import { z } from "zod";
export const CreateUserSchema = z.object({
email: z.string().email("Invalid email format"),
username: z.string().min(3).max(30),
role: z.enum(["developer", "architect", "admin"]).default("developer"),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;3. Validating In Route Handlers
In Next.js App Router route handlers, parse incoming requests using `safeParse()` to avoid unhandled try/catch crashes.
import { NextRequest, NextResponse } from "next/server";
import { CreateUserSchema } from "./schema";
export async function POST(req: NextRequest) {
try {
const raw = await req.json();
const result = CreateUserSchema.safeParse(raw);
if (!result.success) {
return NextResponse.json(
{ errors: result.error.flatten().fieldErrors },
{ status: 422 }
);
}
const validData = result.data; // Fully typed as CreateUserInput
return NextResponse.json({ success: true, user: validData });
} catch {
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
}
}4. Testing With JSON Tools
Use our JSON Formatter and JSON Validator to test your schemas with valid and edge-case payloads before deploying to staging.
Interactive Tools Used in This Guide
Try your queries, payloads, or patterns directly in our free browser developer tools: