zod api validation

Zod API Validation With TypeScript

Validate API requests, responses, fetch results, route params, and errors with Zod parse and safeParse patterns in TypeScript.

Open the JSON to Zod converter

Quick answer: what does Zod API mean?

The search phrase zod api can mean the official Zod API reference, the Express Zod API package, or API validation with Zod schemas. This guide focuses on the third intent: using Zod at TypeScript API boundaries for request bodies, response bodies, fetch wrappers, route params, and structured errors.

Zod API docs vs API validation guide

Use the official Zod API docs when you need method-level reference for `z.object`, `z.string`, `parse`, `safeParse`, refinements, transforms, or codecs. Use this guide when you need implementation patterns for validating HTTP requests, fetch responses, route params, and API errors in TypeScript apps.

  • Official docs answer what each Zod method does.
  • This guide answers where that method belongs in an API boundary.
  • Express Zod API is a separate framework package, not the same thing as Zod itself.

Why TypeScript is not enough for API data

TypeScript types disappear at runtime, while API data arrives from browsers, clients, services, feature flags, and third-party systems. A Zod schema turns an API boundary into an executable contract before untrusted data reaches UI, caching, analytics, or business logic.

  • Use TypeScript for compile-time code safety.
  • Use Zod for runtime validation of data that crossed a network or process boundary.
  • Treat every decoded JSON value as `unknown` until a schema accepts it.

Validate API responses with Zod

Validate responses immediately after `response.json()` so downstream code receives checked data rather than unchecked JSON. Keep the schema close to the client function that owns the endpoint.

import { z } from "zod";

const UserResponseSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  role: z.enum(["admin", "member"]),
  createdAt: z.string().datetime(),
});

type UserResponse = z.infer<typeof UserResponseSchema>;

export async function getUser(userId: string): Promise<UserResponse> {
  const response = await fetch(`/api/users/${userId}`);
  const json: unknown = await response.json();

  return UserResponseSchema.parse(json);
}

Validate API request bodies with Zod

Use Zod before a handler trusts `request.json()` or `req.body`. Request schemas should be stricter than sample data and should include business-facing constraints such as email formats, minimum lengths, enum values, and numeric ranges.

const CreateUserRequestSchema = z.object({
  email: z.string().email(),
  name: z.string().trim().min(2).max(120),
  role: z.enum(["admin", "member"]).default("member"),
});

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

  if (!result.success) {
    return Response.json(
      { error: "Invalid request body", issues: result.error.flatten().fieldErrors },
      { status: 400 },
    );
  }

  return Response.json(await createUser(result.data), { status: 201 });
}

parse vs safeParse for API boundaries

`parse` is best when an invalid API payload should fail fast and be handled by an error boundary, logger, or retry layer. `safeParse` is best when the caller needs to return a controlled HTTP response, UI state, or fallback value.

  • Use `parse` inside typed client functions where invalid upstream data should throw.
  • Use `safeParse` in route handlers, server actions, and UI flows that need structured errors.
  • Use `parseAsync` or `safeParseAsync` when refinements perform asynchronous checks.

Zod API examples

These examples cover the API validation patterns developers usually need first: a checked fetch response, a Next.js Route Handler request body, a small Express middleware, and a reusable typed fetch helper.

import { z } from "zod";

const UserResponseSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  role: z.enum(["admin", "member"]),
});

// 1. Fetch response validation
const json: unknown = await response.json();
const user = UserResponseSchema.parse(json);

// 2. Next.js Route Handler request body
const CreateUserRequestSchema = z.object({
  email: z.string().email(),
  name: z.string().trim().min(2),
});

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

  if (!result.success) {
    return Response.json(
      { error: "Invalid request body", fieldErrors: result.error.flatten().fieldErrors },
      { status: 400 },
    );
  }

  return Response.json(await createUser(result.data), { status: 201 });
}

// 3. Express middleware
function validateBody<TSchema extends z.ZodType>(schema: TSchema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({ issues: result.error.issues });
    }
    req.body = result.data;
    return next();
  };
}

// 4. Reusable fetchJson helper
async function fetchJson<TSchema extends z.ZodType>(
  url: string,
  schema: TSchema,
): Promise<z.infer<TSchema>> {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`Request failed with ${response.status}`);
  return schema.parse(await response.json());
}

Reusable fetch wrapper

A reusable fetch helper can accept a Zod schema, keep the raw response as `unknown`, validate it once, and return the inferred type to the rest of the app.

async function fetchJson<TSchema extends z.ZodType>(
  url: string,
  schema: TSchema,
): Promise<z.infer<TSchema>> {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`Request failed with ${response.status}`);
  }

  const json: unknown = await response.json();
  return schema.parse(json);
}

const users = await fetchJson(
  "/api/users",
  z.array(UserResponseSchema),
);

API error handling pattern

Use `safeParse` when validation failures should become structured API errors. Log the Zod issue near the boundary, but return a concise response that clients can handle predictably.

const result = UserResponseSchema.safeParse(json);

if (!result.success) {
  reportApiContractError({
    endpoint: "/api/users/:id",
    issues: result.error.issues,
  });

  return {
    ok: false,
    error: "API response did not match the expected schema",
  };
}

return { ok: true, data: result.data };

Common Zod API schema patterns

API payloads often need more than plain strings and numbers. Review optional fields, nullable fields, enums, arrays, and dates before treating a generated schema as production-ready.

  • Use `.optional()` when a field may be omitted and `.nullable()` when an API returns `null`.
  • Use `z.enum()` for status, role, type, currency, and other closed string sets.
  • Use `z.array(ItemSchema)` for collection endpoints and nested object lists.
  • Use `.datetime()` for ISO date strings unless the code intentionally converts strings into `Date` objects.

Testing API schemas

Schema tests should cover the payloads that matter to the endpoint rather than only testing the happy path. Keep fixtures close to the client or route handler so contract drift is easy to spot.

import { describe, expect, it } from "vitest";

describe("UserResponseSchema", () => {
  it("accepts the API response shape used by the client", () => {
    expect(
      UserResponseSchema.safeParse({
        id: "4f0881b5-9b09-4f2f-9f77-b1a6ed8b2c18",
        email: "ada@example.com",
        role: "member",
        createdAt: "2026-05-16T10:00:00.000Z",
      }).success,
    ).toBe(true);
  });
});

Production checklist

Before shipping Zod API validation, review fields that a sample payload cannot fully explain. Formats, enum choices, pagination wrappers, and backward-compatible optional fields often need a human pass.

  • Generate a starter schema with the JSON to Zod converter from a real API payload, then rename it after the endpoint or resource.
  • Add `.url()`, `.email()`, `.uuid()`, `.datetime()`, `.min()`, and `.max()` where the API contract promises them.
  • Add fixtures for successful responses, invalid requests, nullable fields, missing optional fields, and malformed payloads.
  • Decide whether OpenAPI, Zod, or generated code owns the source of truth for each API contract.

Zod API boundary map

API boundaryZod methodCode pattern
Request bodies`safeParse` before business logic.Return a controlled 400 response with flattened field errors.
Response bodies`parse` or `safeParse` after `response.json()`.Keep raw JSON as `unknown` until the response schema accepts it.
Query params`safeParse` after normalizing `URLSearchParams`.Return typed filters, pagination, and search values to the handler.
Route params`safeParse` before loading records.Validate ids, slugs, and catch-all params at the framework boundary.
Errors`error.flatten()` or `error.issues`.Expose stable client errors while keeping raw payloads out of public responses.
Tests`safeParse` in fixtures.Cover valid, invalid, nullable, optional, and malformed API payloads.

Use FrameworkKit to generate the starter code, then review the output before shipping it in production.

Generate with the JSON to Zod schema generator

Zod validation resources

JSON to Zod converter

Convert sample payloads into copy-ready Zod schemas and inferred TypeScript types in the browser.

JSON to Zod schema examples

Open realistic API and product payload examples before adapting the JSON to Zod schema output.

TypeScript to Zod Converter

Convert TypeScript interfaces and type aliases into Zod schemas when the source shape already lives in code.

Zod to JSON Schema Converter

Use the canonical online converter when an existing Zod schema needs JSON Schema, AJV, or OpenAPI output.

Zod parse vs safeParse

Choose between parse, safeParse, parseAsync, and safeParseAsync for TypeScript validation flows.

Zod safeParse

Validate unknown data without throwing, narrow the result type, and format safeParse errors.

Zod safeParse error messages

Format safeParse failures, field errors, and API validation responses in TypeScript.

Zod refine vs superRefine

Add custom validation, async checks, and field-level error paths after generating a starter schema.

Zod nativeEnum in Zod 4

Migrate z.nativeEnum patterns to z.enum, validate TypeScript enums, and avoid enum value mistakes.

Use JSON to Zod for form validation

Start from a submitted form payload, then add business rules such as email, length, and enum checks.

Zod to JSON Schema for OpenAPI

Publish Zod API schemas as OpenAPI-compatible contracts when teams need portable documentation.

ZodError flatten vs format

Shape validation errors with z.flattenError() for forms and z.treeifyError() for nested data in Zod 4.

Zod coerce for query params

Turn string query params and form fields into numbers, dates, and booleans, and avoid the coerce.boolean trap.

Zod email validation (z.email)

Validate emails with the top-level z.email() in Zod 4, add custom messages, and migrate off z.string().email().

Zod transform and pipe

Reshape validated data with transform, chain a second check with pipe, and use z.codec for reversible conversions.

Zod vs JSON Schema

Choose between TypeScript-first runtime validation and portable schema contracts.

Zod vs Yup vs Valibot

Compare TypeScript validation libraries for API boundaries, forms, server actions, and bundle tradeoffs.

FAQ

Is Zod good for API validation?

Yes. Zod is a good fit for TypeScript API validation because it validates data at runtime and infers TypeScript types from the same schema.

Should I validate API responses with Zod?

Validate API responses with Zod when the data comes from another service, a third-party API, or any boundary where TypeScript cannot guarantee the runtime shape.

Should API request validation use parse or safeParse?

Use `parse` when invalid data should throw and be handled by your error layer. Use `safeParse` when the handler should return a structured 400 response or a recoverable UI state.

Is Zod a replacement for OpenAPI?

No. Zod is excellent for TypeScript runtime validation, while OpenAPI is a portable API contract format. Many teams use Zod in code and OpenAPI for documentation, clients, and external consumers.

How do I create a Zod schema from an API response?

Paste a representative JSON response into a JSON to Zod converter, copy the starter schema, then add stricter rules for formats, enums, optional fields, nullable fields, and business constraints.

Related comparisons

Related tools