Facebook Pixel

How to Write an Express Validation Middleware With Zod

Zod is a great schema validator. Here is how to use it in an Express validation middleware.

How to Write an Express Validation Middleware With Zod

Input validation is non-negotiable. Zod is one of the best validators for TypeScript and JavaScript. Here is how to use it in Express.

Why Validate

You cannot trust the client. Missing fields, wrong types, and oversized payloads cause bugs and security issues. Validate at the boundary, before any logic runs.

Install Zod

npm install zod.

Define a Schema

const { z } = require('zod');

const signupSchema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  password: z.string().min(8),
  age: z.number().int().min(18),
  gender: z.enum(['male', 'female', 'other'])
});

Write the Validation Middleware

const validate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.issues });
  }
  req.body = result.data; // replace with parsed (and coerced) data
  next();
};

Use It

router.post('/signup', validate(signupSchema), signup);

Now the signup handler only runs if the body is valid. req.body is the parsed and coerced data.

Validating Params and Query

const validateParams = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.params);
  if (!result.success) return res.status(400).json({ errors: result.error.issues });
  next();
};

const idSchema = z.object({ id: z.string().length(24) });
router.get('/users/:id', validateParams(idSchema), getUser);

Why Zod

  • TypeScript inference (if you use TS).
  • Great error messages out of the box.
  • Composable schemas.
  • Safe parse: returns success or error instead of throwing.
  • Built-in types: string, number, enum, array, object, optional, nullable.

Common Patterns

  • Email: z.string().email().
  • Password: z.string().min(8).max(128).
  • ObjectId: z.string().length(24) (or use a regex).
  • Enum: z.enum(['male', 'female', 'other']).
  • Array of strings: z.array(z.string()).
  • Optional: z.string().optional().

The Takeaway

Validate Express input with Zod. Define schemas, write a validate(schema) factory that uses safeParse and replaces req.body with parsed data, and apply it per route. Validate params and query the same way. Never trust the client.

Define a Zod schema, write a validate(schema) middleware factory that uses safeParse, returns 400 with errors if invalid, replaces req.body with parsed data if valid, and calls next. Apply per route: router.post('/signup', validate(signupSchema), signup).

TypeScript inference, great error messages, composable schemas, safe parse (returns success or error instead of throwing), and built-in types. It is one of the best modern validators for Node.js.

Same pattern, but validate req.params. Example: const idSchema = z.object({ id: z.string().length(24) }); router.get('/users/:id', validateParams(idSchema), getUser). Use a separate factory that reads req.params.

Returns an object with success: true and data, or success: false and error. Unlike parse, it does not throw. This is what you want in a middleware, where you want to handle errors gracefully instead of catching throws.

z.string().email(). Zod validates the format. For stronger validation, you can chain .refine with a custom check (e.g., block disposable email providers).

Ready to master Node.js completely?

Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.

Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.
Please Login.