Back to Blogs

Building Type-Safe API Routes in Next.js with Zod & TypeScriptBuilding Type-Safe API Routes in Next.js with Zod & TypeScript

10 months ago
Building Type-Safe API Routes in Next.js with Zod & TypeScript

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

ts
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

ts
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

  1. Centralize Schemas: Keep all your Zod schemas in a schema/qw folder to share them between frontend forms and backend routes.
  2. 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