Skip to main content

Express Route Validation

When building web applications with Express, it's crucial to validate data received from clients before processing it. Route validation helps you ensure that the incoming data meets your expected format and constraints, preventing bugs, improving security, and providing better error feedback to your users.

Why Route Validation Matters

Without proper validation, your application is vulnerable to:

  • Bad data: Malformed inputs can cause unexpected behavior
  • Security vulnerabilities: Malicious inputs may lead to attacks like SQL injection
  • Error-prone code: Processing invalid data can cause runtime errors
  • Poor user experience: Users don't receive clear feedback on what went wrong

Let's explore how to implement effective route validation in your Express applications.

Basic Manual Validation

At its simplest, you can manually validate route parameters, query strings, and request bodies in your route handlers:

javascript
app.post('/user', (req, res) => {
const { username, email, age } = req.body;

// Validate required fields
if (!username || !email) {
return res.status(400).json({ error: 'Username and email are required' });
}

// Validate email format with a simple regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({ error: 'Invalid email format' });
}

// Validate age (if provided)
if (age && (isNaN(age) || age < 18)) {
return res.status(400).json({ error: 'Age must be a number and at least 18' });
}

// If validation passes, continue with route handling
res.status(201).json({ message: 'User created successfully' });
});

This approach works for simple cases but becomes unwieldy as your validation rules grow more complex.

Using express-validator

The express-validator library provides a more structured way to handle validations in Express routes. It's based on the popular validator.js library and offers a middleware-based approach.

Installation

First, install the package:

bash
npm install express-validator

Basic Usage

Here's how to validate a route using express-validator:

javascript
const { body, validationResult } = require('express-validator');

app.post('/user', [
// Define validation rules
body('username').notEmpty().withMessage('Username is required'),
body('email').isEmail().withMessage('Must provide a valid email'),
body('age').optional().isInt({ min: 18 }).withMessage('Age must be at least 18'),
], (req, res) => {
// Check for validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

// If validation passes, continue with route handling
res.status(201).json({ message: 'User created successfully' });
});

Validating Different Request Parts

Express-validator can validate different parts of the request:

javascript
const { body, query, param, validationResult } = require('express-validator');

app.get('/products/:id', [
// Validate URL parameter
param('id').isInt().withMessage('Product ID must be an integer'),

// Validate query string parameters
query('filter').optional().isIn(['price', 'rating', 'date']).withMessage('Invalid filter type'),
query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100'),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

// Proceed with valid request
res.json({ message: 'Valid request', id: req.params.id });
});

Custom Validation Logic

You can also implement custom validation logic:

javascript
const { body } = require('express-validator');

app.post('/register', [
body('password')
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
.matches(/[A-Z]/).withMessage('Password must contain an uppercase letter')
.matches(/[a-z]/).withMessage('Password must contain a lowercase letter')
.matches(/[0-9]/).withMessage('Password must contain a number')
.matches(/[!@#$%^&*]/).withMessage('Password must contain a special character'),

body('confirmPassword').custom((value, { req }) => {
if (value !== req.body.password) {
throw new Error('Passwords do not match');
}
return true; // Return true if validation succeeds
}),

// Custom validator that checks if username is already taken
body('username').custom(async (username) => {
// This would typically be a database check
const userExists = await fakeUserDatabase.findByUsername(username);
if (userExists) {
throw new Error('Username already taken');
}
return true;
}),
], handleRegistration);

Validation Middleware

A common pattern is to extract validation into middleware functions for reuse:

javascript
const { body, validationResult } = require('express-validator');

// Create reusable validation middleware
const validateUser = [
body('username').notEmpty().withMessage('Username is required'),
body('email').isEmail().withMessage('Must provide a valid email'),
body('age').optional().isInt({ min: 18 }).withMessage('Age must be at least 18'),
];

// Create middleware to check validation results
const checkValidationResult = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
};

// Use the validation middleware in routes
app.post('/user', validateUser, checkValidationResult, (req, res) => {
// Route handler logic (only executes if validation passes)
res.status(201).json({ message: 'User created successfully' });
});

Using Joi for Validation

Another popular approach is using Joi, a powerful schema description language and validator:

Installation

bash
npm install joi

Basic Usage with Joi

javascript
const express = require('express');
const Joi = require('joi');
const app = express();

app.use(express.json());

// Create a middleware function for Joi validation
const validateRequest = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);

if (error) {
return res.status(400).json({
error: error.details[0].message
});
}

next();
};
};

// Define validation schema
const userSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).optional(),
interests: Joi.array().items(Joi.string()).optional()
});

// Use validation middleware in route
app.post('/user', validateRequest(userSchema), (req, res) => {
res.status(201).json({ message: 'User created successfully' });
});

Real-World Example: Form Submission API

Let's build a more complete example of a form submission API with validation:

javascript
const express = require('express');
const { body, validationResult } = require('express-validator');
const app = express();

app.use(express.json());

// Validation middleware for contact form
const validateContactForm = [
body('name')
.notEmpty().withMessage('Name is required')
.isLength({ min: 2, max: 50 }).withMessage('Name must be between 2 and 50 characters'),

body('email')
.isEmail().withMessage('Must provide a valid email')
.normalizeEmail(),

body('subject')
.notEmpty().withMessage('Subject is required')
.isLength({ min: 5, max: 100 }).withMessage('Subject must be between 5 and 100 characters'),

body('message')
.notEmpty().withMessage('Message is required')
.isLength({ min: 10, max: 1000 }).withMessage('Message must be between 10 and 1000 characters')
.trim(),

body('newsletter')
.optional()
.isBoolean().withMessage('Newsletter field must be a boolean'),

body('source')
.optional()
.isIn(['search', 'social', 'friend', 'other']).withMessage('Invalid source')
];

app.post('/contact', validateContactForm, (req, res) => {
const errors = validationResult(req);

if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}

// Process valid form data
const formData = req.body;

// In a real application, you might:
// - Store in database
// - Send email notification
// - Add to CRM system
// etc.

console.log('Valid contact form received:', formData);

res.status(201).json({
success: true,
message: 'Thank you for your message! We will get back to you shortly.'
});
});

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

Best Practices for Route Validation

  1. Validate All Input: Never trust client-side data, even if your frontend has validation.

  2. Sanitize Data: Always sanitize inputs to prevent injection attacks.

  3. Be Specific: Use the most specific validation rules possible.

  4. Provide Clear Error Messages: Help users understand what they did wrong.

  5. Normalize Data: Convert inputs to consistent formats (e.g., trimming strings, normalizing emails).

  6. Separate Validation Logic: Extract validation into reusable middleware.

  7. Documentation: Document your validation requirements for API clients.

Common Validation Scenarios

Here are some common validation scenarios you might encounter:

User Registration Form

javascript
const registrationValidation = [
body('username')
.notEmpty().withMessage('Username is required')
.isAlphanumeric().withMessage('Username can only contain letters and numbers')
.isLength({ min: 3, max: 20 }).withMessage('Username must be 3-20 characters'),

body('email')
.isEmail().withMessage('Must provide a valid email')
.normalizeEmail(),

body('password')
.isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
.matches(/[A-Z]/).withMessage('Password must include uppercase letter')
.matches(/[a-z]/).withMessage('Password must include lowercase letter')
.matches(/[0-9]/).withMessage('Password must include a number')
];

API Parameters

javascript
const productValidation = [
param('id').isInt().withMessage('Product ID must be an integer'),

query('page')
.optional()
.isInt({ min: 1 }).withMessage('Page must be a positive integer'),

query('sort')
.optional()
.isIn(['name', 'price', 'date']).withMessage('Invalid sort parameter')
];

File Upload

javascript
const fileUploadValidation = [
body('title')
.notEmpty().withMessage('File title is required'),

body('fileType')
.isIn(['image', 'document', 'video']).withMessage('Invalid file type'),

// Express doesn't validate files directly, but you can check if the file exists
// and validate it in the route handler
];

Summary

Route validation is an essential aspect of building robust Express applications. By validating inputs, you can:

  • Prevent bugs and crashes from unexpected data
  • Improve security by rejecting potentially harmful inputs
  • Provide better user experiences with clear error messages
  • Make your code more maintainable and less error-prone

Whether you choose to implement manual validation, use express-validator, Joi, or another validation library, the important thing is to consistently validate all inputs to your application.

Additional Resources

Exercises

  1. Create a validation middleware for a product creation API that validates name, price, category, and an optional description.

  2. Implement a login route with validation for username/email and password.

  3. Build a blog post API with validation for title, content, and tags (as an array).

  4. Create a validation middleware for a search API that validates query parameters like search term, filters, and pagination.

  5. Implement a user profile update API that validates different fields like name, bio, social media links, etc.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)