HomeProjectsAI FilesBlog

© 2026 Matheus Pires. All rights reserved.

Back to blog

Building type-safe APIs with Zod and Next.js

July 9, 2026

Why Type Safety Matters

When building APIs, the gap between backend and frontend types is where most bugs live. You change a field on the server, forget to update the client, and suddenly you have a runtime error that only shows up in production.

Zod solves this by giving you a single source of truth that validates data at runtime and infers TypeScript types at compile time.

The Pattern

The core idea is simple: define your validation schemas once, derive types from them, and reuse those schemas across your entire stack.

import { z } from "zod";

const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  email: z.string().email(),
  role: z.enum(["admin", "user", "guest"]),
});

type User = z.infer<typeof UserSchema>;

Now User is fully typed, and any data coming through your API gets validated against the exact same shape.

Shared Schemas

I keep schemas in a lib/schemas directory and import them from both server and client code:

lib/
  schemas/
    user.ts
    project.ts
    ai-file.ts

When a Next.js route handler receives a request body, it validates with the same schema the frontend uses for form validation:

// app/api/users/route.ts
import { UserSchema } from "@/lib/schemas/user";

export async function POST(request: Request) {
  const body = await request.json();
  const result = UserSchema.safeParse(body);

  if (!result.success) {
    return Response.json(result.error, { status: 400 });
  }

  // result.data is fully typed
}

Form Validation on the Client

The same schema can power your form validation without any extra work:

const form = useForm({
  schema: UserSchema,
  onSubmit: async (values) => {
    // values is already typed as User
    await fetch("/api/users", {
      method: "POST",
      body: JSON.stringify(values),
    });
  },
});

What I Gained

After adopting this pattern across all my projects:

  • Zero type drift between API contracts
  • Runtime validation that catches bad data before it hits your database
  • Autocomplete everywhere from a single schema definition
  • Less code because validation logic isn't duplicated

The initial setup takes a bit of planning, but the long-term payoff in reliability and developer experience is significant.