Blog
Wild & Free Tools

Validate Express Request Bodies with Zod

Last updated: April 2026 6 min read

Table of Contents

  1. Inline Validation Pattern
  2. Reusable Validation Middleware
  3. Generating Schemas from Real Request Bodies
  4. Returning Structured Validation Errors
  5. Frequently Asked Questions

To validate Express request bodies with Zod, define a schema for the expected body shape, call schema.safeParse(req.body) at the start of your route handler, and return a 400 response with structured errors if validation fails. The validated data is then fully typed for the rest of the handler.

This guide shows the inline validation pattern, a reusable middleware approach, and how to generate schemas from real JSON samples.

Inline Validation Pattern

The simplest approach — validate at the top of each route handler:

import express from 'express';
import { z } from 'zod';

const app = express();
app.use(express.json());

const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional()
});

app.post('/users', (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);

  if (!result.success) {
    return res.status(400).json({
      error: 'Validation failed',
      issues: result.error.issues
    });
  }

  const { name, email, age } = result.data; // fully typed
  // ... create user
  res.status(201).json({ message: 'Created' });
});

This works fine for small projects or one-off routes. For larger APIs, the middleware approach below keeps handlers cleaner.

Reusable Validation Middleware

Create a middleware factory that accepts any Zod schema:

import { Request, Response, NextFunction } from 'express';
import { ZodSchema } from 'zod';

function validateBody(schema: ZodSchema) {
  return (req: Request, res: Response, next: NextFunction) => {
    const result = schema.safeParse(req.body);

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

    req.body = result.data;
    next();
  };
}

// Usage
app.post('/users', validateBody(CreateUserSchema), (req, res) => {
  const { name, email } = req.body; // already validated
  // ...
});
Sell Custom Apparel — We Handle Printing & Free Shipping

Generating Schemas from Real Request Bodies

If you have a sample request body (from Postman, curl, or existing logs), paste it into the JSON to Zod converter to generate the base schema:

{
  "name": "Alice",
  "email": "[email protected]",
  "age": 28,
  "preferences": {
    "newsletter": true,
    "theme": "dark"
  }
}

The converter outputs:

const schema = z.object({
  name: z.string(),
  email: z.string(),
  age: z.number(),
  preferences: z.object({
    newsletter: z.boolean(),
    theme: z.string()
  })
});

Then add constraints: z.string().email(), z.number().int().min(0), etc.

Returning Structured Validation Errors

Zod provides two error formats:

// issues format
[
  { path: ['email'], message: 'Invalid email', code: 'invalid_string' },
  { path: ['age'], message: 'Expected number, received string', code: 'invalid_type' }
]

// flatten().fieldErrors format
{
  email: ['Invalid email'],
  age: ['Expected number, received string']
}

Use flatten().fieldErrors when returning errors to client-side forms — it maps directly to field names. Use issues for logging or debugging.

Try It Free — No Signup Required

Runs 100% in your browser. No data is collected, stored, or sent anywhere.

Open Free JSON to Zod Converter

Frequently Asked Questions

How do I validate req.body with Zod in Express?

Call YourSchema.safeParse(req.body). If result.success is false, return a 400 with result.error.issues or result.error.flatten().fieldErrors. If true, result.data is typed and validated.

Should I use middleware or inline validation for Express + Zod?

Inline validation is simpler for a few routes. For larger APIs, a validateBody(schema) middleware factory keeps handlers clean and puts all validation logic in one place.

Can Zod validate query parameters and URL params in Express?

Yes. Use schema.safeParse(req.query) or schema.safeParse(req.params). Note that all query/param values are strings by default — use z.coerce.number() to convert string numbers automatically.

What is the difference between result.error.issues and flatten().fieldErrors?

issues is a flat array of all validation errors. flatten().fieldErrors returns an object keyed by field name, each containing an array of error messages. Use fieldErrors for returning errors to forms.

Launch Your Own Clothing Brand — No Inventory, No Risk