Building Type-Safe API Routes in Next.js with Zod & TypeScript
Building APIs in Next.js without type safety is like debugging in the dark. You assume the data is correct, but one missing field crashes your app.
The Problem I Faced
I was working on a user update feature where the frontend sent a slightly different payload than the backend expected. Result? Silent failures and hours of debugging. I needed a way to guarantee that incoming data matched exactly what my code expected, instantly.
Understanding Zod Validation
Zod is a TypeScript-first schema declaration and validation library. It allows you to verify data at runtime (when the API is hit) while giving you static type inference during development.
Code Example 1: Defining the Schema
import { z } from "zod";
const updateUserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
preferences: z.object({
theme: z.enum(["light", "dark"]),
notifications: z.boolean(),
}),
});
// Infer TypeScript type automatically
type UpdateUserBody = z.infer<typeof updateUserSchema>;Code Example 2: API Route Implementation
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const body = await request.json();
// Validate request body
const result = updateUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: "Invalid data", details: result.error.format() },
{ status: 400 }
);
}
const { email, role } = result.data;
// Proceed with database update reliably...
return NextResponse.json({ success: true, email });
} catch (error) {
return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
}
}Common Pitfalls
-
Mistake 1: Trusting frontend validation only.
- Don't: Assume the client sending the request is your own frontend.
- Do: Always validate on the server with Zod.
-
Mistake 2: Manually defining interfaces that drift from validation logic.
- Don't: Write a TypeScript interface and a separate validation function.
- Do: Use
z.infer<typeof schema>to keep them 100% in sync.
Pro Tips
- Centralize Schemas: Keep all your Zod schemas in a
schema/qwfolder to share them between frontend forms and backend routes. - Error Formatting: Use formatted Zod errors to give the frontend helpful feedback.
Wrapping Up
By adding Zod, we turned runtime uncertainty into compile-time confidence. It makes refactoring easier and catches bugs before they even hit the database.
Tech Stack: Next.js, Zod, TypeScript
