Data Validation in Node.js Express: A Step-by-Step Guide to Joi Middleware

When building APIs, ensuring the data sent by users meets specific requirements is essential for maintaining a secure and functional application. One way to enforce data integrity is through validation. In this post, we’ll walk through setting up Joi middleware for request body validation in a Node.js and Express app. We'll cover schema creation, integrating it as middleware, and customizing the validation process.
Why Joi for Validation?
Joi is a powerful data validation library for JavaScript, especially useful in Node.js applications. With Joi, you can create complex validation schemas and easily reuse them, ensuring that incoming data is thoroughly checked before any processing begins.
Creating Joi Validation Schemas
In this example, we’ll set up validation for two scenarios:
- User Registration
- User Login
User Registration Schema
For registering a new user, we want to ensure that essential details are provided. Here’s the validation schema for the registration request body:
import Joi from 'joi';
const userRegisterSchema = Joi.object({
username: Joi.string().min(8).required(),
email_address: Joi.string().email().required(),
password: Joi.string().min(8).required(),
full_name: Joi.string().required(),
});
In this schema:
- username is a required string with a minimum length of 8 characters.
- email_address is a required valid email.
- password is a required string with a minimum length of 8 characters.
- full_name is a required string, ensuring the user provides a name.
User Login Schema
Similarly, for login, we need to validate credentials:
const userLoginSchema = Joi.object({
login_id: Joi.string().required(),
password: Joi.string().min(8).required(),
});
For login:
- login_id is a required string (which could be a username or email).
- password is a required string with a minimum length of 8 characters.
Creating Validation Middleware
The Joi schemas on their own are not sufficient to validate incoming requests. We need to integrate them into Express as middleware functions to automatically validate requests for each route.
Validation Middleware for Registration
import { Request, Response, NextFunction } from 'express';
export const validateUserRegisterInput = (req: Request, res: Response, next: NextFunction) => {
const { error } = userRegisterSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
};
Validation Middleware for Login
export const validateUserLoginInput = (req: Request, res: Response, next: NextFunction) => {
const { error } = userLoginSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
};
In both middleware functions:
req.body
is validated against the relevant Joi schema.- If there’s an error, it returns a
400 Bad Request
status with a detailed message. - If validation passes,
next()
calls the next middleware or route handler.
Integrating Middleware with Routes
With our validation middleware ready, we can apply it to our routes. Here’s an example of how to set up registration and login routes with validation:
import express from 'express';
import { validateUserRegisterInput, validateUserLoginInput } from './validation'; // adjust path as needed
const app = express();
app.use(express.json());
app.post('/register', validateUserRegisterInput, (req, res) => {
// Handle registration logic here
res.status(201).json({ message: 'User registered successfully!' });
});
app.post('/login', validateUserLoginInput, (req, res) => {
// Handle login logic here
res.status(200).json({ message: 'User logged in successfully!' });
});
app.listen(3000, () => console.log('Server is running on port 3000'));
Now:
- The
validateUserRegisterInput
middleware will automatically validate requests to the/register
endpoint. - The
validateUserLoginInput
middleware does the same for the/login
endpoint.
If a request body fails validation, the user receives a 400 Bad Request
response with an error message, stopping further processing.
Customizing Error Messages
You can customize the error messages by adjusting the Joi schemas. For example, to make the error messages more user-friendly, modify the schema as follows:
const userRegisterSchema = Joi.object({
username: Joi.string().min(8).required().messages({
'string.min': 'Username should be at least 8 characters long.',
'any.required': 'Username is required.',
}),
email_address: Joi.string().email().required().messages({
'string.email': 'Please provide a valid email address.',
'any.required': 'Email address is required.',
}),
password: Joi.string().min(8).required().messages({
'string.min': 'Password should be at least 8 characters long.',
'any.required': 'Password is required.',
}),
full_name: Joi.string().required().messages({
'any.required': 'Full name is required.',
}),
});
This approach makes it easier to deliver user-friendly feedback on what went wrong during validation.
With Joi validation middleware, you ensure your API’s request data is secure, complete, and error-free. This setup:
- Provides immediate feedback to users when data is missing or incorrect.
- Prevents unnecessary errors down the line.
- Keep your application code clean by handling validation separately.
Now, you have a robust setup for validating request data in your Node.js Express app using Joi middleware.
For more NodeJS concepts and articles, you can check this page dedicated to NodeJS

That's it for today. See ya 👋