09 Validation Joi Zod
title: Validation with Joi and Zod in Node.js REST APIs weight: 19 date: 2026-06-28 lastmod: 2026-06-28 description: Learn request validation with Joi and Zod in Node.js REST APIs including schema definition, middleware integration, custom error messages, and validation testing. tags: [api-development, nodejs]
Validation with Joi and Zod defines schemas for request data in Node.js REST APIs, ensuring inputs meet requirements before reaching business logic and returning detailed error messages for invalid data.
```mermaid
flowchart TD
A[Incoming Request] --> B[Validation Middleware]
B --> C{Valid?}
C -->|Yes| D[Route Handler]
C -->|No| E[400 + Validation Details]
D --> F[Business Logic]
style A fill:#e1f5fe
style B fill:#fff9c4
style C fill:#c8e6c9
style E fill:#ffcdd2
Joi and Zod are schema validation libraries. Joi is the traditional choice with a fluent API. Zod is newer with TypeScript-first design and type inference. Both define schemas for objects, strings, numbers, arrays, and custom validations, and return detailed error arrays on failure.
Think of validation like a bouncer at a club. The schema defines the rules: must be over 21 (minimum age), must have an ID (required field), no weapons (invalid patterns). The bouncer checks everyone and turns away those who do not meet the requirements.
Example: Joi Schema Validation
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(2).max(50).required()
.messages({ 'string.min': 'Name must be at least 2 characters' }),
email: Joi.string().email().required(),
age: Joi.number().integer().min(0).max(150),
role: Joi.string().valid('admin', 'editor', 'viewer').default('viewer'),
tags: Joi.array().items(Joi.string()).max(5)
});
// Validation middleware
const validate = (schema) => {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, {
abortEarly: false,
stripUnknown: true
});
if (error) {
const details = error.details.map(d => ({
field: d.path.join('.'),
message: d.message
}));
return res.status(400).json({
status: 'error',
error: { code: 'VALIDATION_ERROR', details }
});
}
req.body = value;
next();
};
};
app.post('/api/users', validate(userSchema), userController.createUser);
Example: Zod Schema Validation
const { z } = require('zod');
const UserSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters').max(50),
email: z.string().email('Invalid email format'),
age: z.number().int().positive().optional(),
role: z.enum(['admin', 'editor', 'viewer']).default('viewer'),
tags: z.array(z.string()).max(5).optional()
});
// Type inference for TypeScript
type User = z.infer<typeof UserSchema>;
// Validation middleware
const validateZod = (schema) => (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
const details = result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message
}));
return res.status(400).json({
status: 'error',
error: { code: 'VALIDATION_ERROR', details }
});
}
req.body = result.data;
next();
};
Example: Testing Validation
// Joi test
const { validateUser } = require('./validation');
test('validates correct user data', () => {
const validUser = {
name: 'Alice',
email: 'alice@example.com',
age: 30,
role: 'admin'
};
const { error, value } = userSchema.validate(validUser);
expect(error).toBeUndefined();
expect(value.name).toBe('Alice');
});
test('rejects invalid email', () => {
const invalidUser = {
name: 'Bob',
email: 'not-an-email'
};
const { error } = userSchema.validate(invalidUser);
expect(error).toBeDefined();
expect(error.details[0].path).toContain('email');
});
Expected output:
PASS tests/validation.test.js
✓ validates correct user data (3 ms)
✓ rejects invalid email (1 ms)
Common Mistakes
- Validating only in the route handler — Validation should be middleware that runs before the handler. This keeps handlers clean and validation reusable.
- Not stripping unknown fields — Without stripUnknown, extra fields pass through and may cause security issues. Strip fields not in the schema.
- Aborting early on first error — abortEarly: false in Joi returns all validation errors, not just the first. This helps clients fix all issues at once.
- Throwing validation errors directly — Return validation errors as 400 responses, not 500. Use a validation middleware that returns formatted errors.
- Not validating all input sources — Validate req.body, req.params, req.query, and req.headers separately with appropriate schemas.
Practice Questions
- What is the difference between Joi and Zod?
- Why should validation be middleware instead of inline in route handlers?
- What does stripUnknown do in Joi?
- How do you validate query parameters with these libraries?
- Challenge: Create validation schemas for a user registration endpoint with Joi and Zod. Include name, email, password (min 8 chars, must have number and special char), and confirmPassword (must match password). Build middleware that validates body, params, and query.
FAQ
Mini Project
Build a validation middleware system for an Express API. Create schemas for user registration, order creation, and product filtering. The middleware should validate body, params, and query separately, return formatted error responses, and integrate with the error handling system.
What's Next
Now learn about authentication with JWT in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro