Node.js Request Validation with Joi and Zod

Implementing request validation is crucial for securing and ensuring the reliability of your Node.js APIs. This article provides a straightforward guide on how to validate incoming request data using two of the most popular validation libraries in the JavaScript ecosystem: Joi and Zod. You will learn how to define schemas, create reusable validation middleware for Express, and handle validation errors effectively.

Why Validate API Requests?

API request validation ensures that incoming data conforms to a specified format before your application processes it. This prevents database corruption, application crashes, and security vulnerabilities like SQL injection or malicious payload execution. By validating payloads at the entry point of your application, you can return clear, immediate feedback to the client when a request is malformed.


Request Validation Using Joi

Joi is a powerful, schema-based description language and validator for JavaScript objects. It is highly readable and has been a staple in the Node.js community for years.

1. Install Joi

To get started, install Joi in your Node.js project:

npm install joi

2. Define a Joi Schema

Create a schema that defines the expected structure of your request payload (for example, a user registration request):

const Joi = require('joi');

const userRegistrationSchema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  email: Joi.string().email().required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required(),
});

3. Create Joi Middleware

You can build a reusable Express middleware function to validate incoming payloads against your Joi schema:

const validateBody = (schema) => {
  return (req, res, next) => {
    const { error, value } = schema.validate(req.body, { abortEarly: false });
    
    if (error) {
      const errorDetails = error.details.map(detail => detail.message);
      return res.status(400).json({ errors: errorDetails });
    }
    
    // Replace req.body with the validated (and sanitized) value
    req.body = value;
    next();
  };
};

Request Validation Using Zod

Zod is a TypeScript-first schema declaration and validation library. It is highly favored in modern Node.js environments because it automatically infers TypeScript types from your validation schemas, eliminating redundancy.

1. Install Zod

Install Zod in your project:

npm install zod

2. Define a Zod Schema

Define your validation schema using Zod’s chainable methods:

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

const userRegistrationSchema = z.object({
  username: z.string().min(3).max(30).regex(/^[a-zA-Z0-9]+$/),
  email: z.string().email(),
  password: z.string().min(6),
});

3. Create Zod Middleware

Implement a reusable middleware function to parse and validate request data using Zod:

const validateBody = (schema) => {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    
    if (!result.success) {
      const errorDetails = result.error.issues.map(issue => ({
        field: issue.path.join('.'),
        message: issue.message
      }));
      return res.status(400).json({ errors: errorDetails });
    }
    
    // Replace req.body with the parsed and typed value
    req.body = result.data;
    next();
  };
};

Integrating Validation in Express Routes

Once your schemas and middleware are defined, integrating them into your Express routes is straightforward. Apply the validation middleware directly to the route definitions before your controller logic:

const express = require('express');
const app = express();

app.use(express.json());

// Example route using either Joi or Zod middleware
app.post('/api/register', validateBody(userRegistrationSchema), (req, res) => {
  // If the request reaches this handler, the data is guaranteed to be valid
  res.status(201).json({ message: "User registered successfully", data: req.body });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Joi vs. Zod: Which Should You Choose?