celebrate vs express-joi-validation vs express-validator vs joi
Input Validation Strategies in Express.js Applications
celebrateexpress-joi-validationexpress-validatorjoiSimilar Packages:

Input Validation Strategies in Express.js Applications

joi is a powerful schema description language and data validator for JavaScript objects, serving as the core engine for validation logic. celebrate, express-joi-validation, and express-validator are middleware wrappers designed to integrate validation into Express.js request lifecycles. While express-validator uses its own internal chainable API, both celebrate and express-joi-validation rely on joi schemas to define rules. These tools automate the process of checking request bodies, queries, parameters, and headers, returning standardized error responses when data does not match the defined structure, thereby reducing boilerplate code and improving application security.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
celebrate01,34727.2 kB0a month agoMIT
express-joi-validation010321.9 kB10a year agoMIT
express-validator06,233146 kB786 months agoMIT
joi021,1721.89 MB20116 days agoBSD-3-Clause

Input Validation in Express: Joi, Celebrate, Express-Joi-Validation, and Express-Validator

Building reliable APIs requires strict input validation. Without it, your application risks security vulnerabilities, data corruption, and unpredictable behavior. In the Express.js ecosystem, developers typically choose between using a standalone schema engine like joi or adopting middleware wrappers such as celebrate, express-joi-validation, or express-validator. Let's break down how these tools differ in architecture, syntax, and real-world usage.

๐Ÿ—๏ธ Core Architecture: Engine vs. Middleware

The fundamental difference lies in whether the package is a validation engine or a middleware adapter.

joi is the engine. It defines what valid data looks like but does not know how to handle HTTP requests. You use it to compile schemas and validate plain JavaScript objects. It works anywhere โ€” in CLI tools, background workers, or frontend code โ€” not just in Express.

// joi: Standalone validation engine
const Joi = require('joi');

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

const { error, value } = schema.validate({ username: 'abc', password: '123' });
if (error) console.log(error.message);

celebrate and express-joi-validation are middleware layers. They do not contain validation logic themselves; instead, they accept joi schemas and apply them to specific parts of an Express request (body, query, params, headers). They act as the bridge between your joi definitions and the Express lifecycle.

// celebrate: Middleware wrapping Joi
const { celebrate, Joi, segments } = require('celebrate');

app.post('/user', celebrate({
  [segments.BODY]: Joi.object({
    username: Joi.string().required()
  })
}), (req, res) => {
  // If validation passes, code reaches here
  res.json({ message: 'User created' });
});
// express-joi-validation: Alternative middleware wrapping Joi
const expressJoiValidation = require('express-joi-validation');
const Joi = require('joi');
const validator = expressJoiValidation.createValidator();

const schema = validator.object({
  username: Joi.string().required()
});

app.post('/user', validator.body(schema), (req, res) => {
  res.json({ message: 'User created' });
});

express-validator takes a different path. It bundles both the validation logic and the middleware into one package. It does not use joi. Instead, it provides a chainable API based on validator.js functions. This means you don't need to manage peer dependencies, but you also lose the powerful schema composition features of joi.

// express-validator: Self-contained chainable API
const { body, validationResult } = require('express-validator');

app.post('/user', 
  body('username').isAlphanumeric().isLength({ min: 3 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    res.json({ message: 'User created' });
  }
);

๐Ÿ“ Defining Rules: Schema Objects vs. Chains

How you write your validation rules drastically changes the developer experience.

joi (and by extension celebrate and express-joi-validation) uses a declarative schema object approach. You define the shape of the entire data structure at once. This makes it easy to visualize complex nested objects and enforce relationships between fields.

// Joi-style schema (used in celebrate/express-joi-validation)
const schema = Joi.object({
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(18).max(100),
  settings: Joi.object({
    theme: Joi.string().valid('dark', 'light'),
    notifications: Joi.boolean().default(true)
  })
});

express-validator uses an imperative, chainable style. You attach validators directly to field names. This can feel more natural if you are used to writing linear code, but it can become verbose when validating deeply nested structures or when you need to validate multiple fields with similar rules.

// express-validator chainable style
const validationRules = [
  body('email').isEmail().normalizeEmail(),
  body('age').optional().isInt({ min: 18, max: 100 }),
  body('settings.theme').optional().isIn(['dark', 'light']),
  body('settings.notifications').optional().isBoolean()
];

โš ๏ธ Error Handling: Automatic vs. Manual

One of the biggest pain points in Express is consistent error handling. The packages handle this differently.

celebrate automatically catches validation errors and passes them to your Express error handler. You do not need to check for errors in every route. If validation fails, celebrate throws a specific CelebrateError that your global error middleware can catch and format.

// celebrate: Automatic error passing
// No need to check errors inside the route handler
app.post('/login', celebrate({ /* schema */ }), (req, res) => {
  // This only runs if valid
  res.send('Logged in');
});

// Global error handler needed elsewhere
app.use((err, req, res, next) => {
  if (err.isJoi) {
    res.status(400).json(err.details);
  }
});

express-joi-validation similarly intercepts errors but allows you to configure how they are returned. It integrates smoothly with standard Express error handling patterns, ensuring that invalid requests never reach your business logic.

// express-joi-validation: Intercepts and forwards errors
app.post('/login', validator.body(schema), (req, res) => {
  // Safe to assume data is valid here
  res.send('Logged in');
});

express-validator requires you to manually check for errors in every route using validationResult(req). This gives you full control over the response format but introduces boilerplate code in every single endpoint. If you forget to check, invalid data might slip through or your app might crash later.

// express-validator: Manual error checking required
app.post('/login', 
  body('email').isEmail(), 
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      // You must explicitly handle the error here
      return res.status(400).json({ errors: errors.array() });
    }
    res.send('Logged in');
  }
);

joi alone provides no HTTP error handling. You must wrap the validate call in try-catch blocks or conditional checks and manually construct HTTP responses.

// joi: Manual HTTP handling required
app.post('/login', (req, res) => {
  const { error, value } = schema.validate(req.body);
  if (error) {
    return res.status(400).json({ message: error.message });
  }
  res.send('Logged in');
});

๐Ÿงน Sanitization: Cleaning Data

Validation checks if data is correct; sanitization changes data to make it safe or consistent.

express-validator excels here. It has built-in sanitizers like trim(), normalizeEmail(), and escape() chained directly with validators. This keeps validation and cleaning logic in one place.

// express-validator: Built-in sanitization
body('email').trim().normalizeEmail(),
body('comment').trim().escape()

joi (and its wrappers) handles sanitization differently. It uses rules like .trim(), .lowercase(), or .default() within the schema definition. When validation succeeds, the returned value object contains the sanitized data. You must use this returned value, not the original request body.

// Joi-style: Sanitization via schema transformation
const schema = Joi.object({
  email: Joi.string().email().trim().lowercase().required(),
  role: Joi.string().valid('user', 'admin').default('user')
});
// The 'value' returned after validation contains the cleaned email and default role

๐Ÿ“ฆ Dependency Management

Dependencies affect your project's complexity and update strategy.

  • joi: Single dependency. Lightweight if you only need validation logic.
  • celebrate: Requires joi as a peer dependency. You must install both celebrate and joi. This ensures you control the joi version explicitly.
  • express-joi-validation: Also requires joi as a peer dependency. Same model as celebrate.
  • express-validator: Zero peer dependencies. It bundles everything. This simplifies installation but means you are tied to the validation engine version shipped with the package.

๐Ÿ›‘ Deprecation and Maintenance Warning

Before choosing, check the current status of the libraries. Historically, some wrappers around joi have fallen behind as joi itself underwent major breaking changes (moving away from browser support and changing API signatures).

  • celebrate is generally regarded as the modern standard for joi + Express integration. It actively tracks joi updates.
  • express-joi-validation has seen periods of slower maintenance. While functional, teams should verify its compatibility with the latest joi versions before adopting it for new, long-term projects. If the repository shows long gaps in releases, celebrate is the safer bet.
  • express-validator is highly active and maintains its own ecosystem independently of joi.

๐Ÿ’ก Real-World Selection Guide

Scenario 1: The Modern Express API

You are building a new REST API with Express. You want strong typing, clear schemas, and automatic error handling.

  • Choice: celebrate + joi
  • Why: You get the power of joi schemas with the convenience of automatic middleware error handling. The separation of schema definition and route logic keeps code clean.

Scenario 2: The Minimalist / No-Extra-Deps Approach

You want to add validation quickly without managing multiple packages or peer dependencies.

  • Choice: express-validator
  • Why: One install, one API. The chainable syntax is easy to learn, and built-in sanitization reduces the need for extra utility functions.

Scenario 3: Non-Express Environments

You are validating data in a script, a queue worker, or a frontend form handler.

  • Choice: joi
  • Why: The other packages are Express middleware and won't work here. joi is universal.

Scenario 4: Legacy Migration

You are maintaining an older Express app already using express-joi-validation.

  • Choice: Stick with express-joi-validation OR migrate to celebrate
  • Why: If it works, don't break it. However, if you encounter compatibility issues with newer joi versions, migrating to celebrate is the recommended path forward due to its active maintenance.

๐Ÿ“Š Summary Comparison

Featurejoicelebrateexpress-joi-validationexpress-validator
TypeValidation EngineExpress MiddlewareExpress MiddlewareExpress Middleware
Schema StyleObject DeclarationObject Declaration (via Joi)Object Declaration (via Joi)Chainable Functions
DependenciesNoneRequires joiRequires joiNone (Self-contained)
Error HandlingManualAutomatic (throws to handler)Automatic (forwards to handler)Manual (validationResult)
SanitizationVia Schema TransformVia Schema TransformVia Schema TransformBuilt-in Chainable Methods
Best ForUniversal ValidationModern Express AppsLegacy/Specific Joi NeedsQuick Setup / No Peer Deps

๐Ÿ’ก Final Recommendation

For most new Express.js projects, celebrate paired with joi offers the best balance of power and developer experience. The ability to define complex schemas declaratively and have errors handled automatically leads to cleaner, more maintainable code.

If you prefer to avoid peer dependencies or like the chainable API style, express-validator is a robust, battle-tested alternative that removes the need to manage joi separately.

Avoid using raw joi inside Express routes unless you have a very specific reason, as you will end up rewriting the error handling logic that celebrate or express-validator already solves for you. Always verify the maintenance status of express-joi-validation before adoption, as celebrate has become the preferred community standard for Joi-based middleware.

How to Choose: celebrate vs express-joi-validation vs express-validator vs joi

  • celebrate:

    Choose celebrate if you want a modern, actively maintained middleware that tightly integrates joi schemas with Express. It is ideal for teams that prefer the joi syntax for defining rules but need automatic error handling and a clean separation of concerns. Note that you must pair this with joi as a separate dependency, as celebrate does not bundle it.

  • express-joi-validation:

    Choose express-joi-validation if you need a lightweight wrapper around joi that allows for flexible injection of validated data into your route handlers. It is suitable for projects that already rely heavily on joi and want a simple middleware solution, though you should verify its current maintenance status compared to celebrate before committing to it for long-term enterprise projects.

  • express-validator:

    Choose express-validator if you prefer a self-contained solution that does not require an external schema library like joi. It is excellent for teams that want a single dependency with a chainable API that mimics validator.js, offering built-in sanitization and validation without needing to manage peer dependencies or schema compilation steps.

  • joi:

    Choose joi if you need a robust, framework-agnostic validation engine for Node.js applications that are not built on Express, or if you require deep customization of validation logic outside of HTTP middleware. It is the foundational library for the other packages listed, so select it when you want full control over schema definition without the overhead of Express-specific middleware integration.

README for celebrate

celebrate

Current Version Build Status Neo Standard Code Coverage 18mo Downloads

celebrate is an express middleware function that wraps the joi validation library. This allows you to use this middleware in any single route, or globally, and ensure that all of your inputs are correct before any handler function. The middleware allows you to validate req.params, req.headers, and req.query.

The middleware will also validate:

celebrate lists joi as a formal dependency. This means that celebrate will always use a predictable, known version of joi during the validation and compilation steps. There are two reasons for this:

  1. To ensure that celebrate can always use the latest version of joi as soon as it's published
  2. So that celebrate can export the version of joi it uses to the consumer to maximize compatibility

express Compatibility

celebrate is tested and has full compatibility with express 4 and 5. It likely works correctly with express 3, but including it in the test matrix was more trouble than it's worth. This is primarily because express 3 exposes route parameters as an array rather than an object.

Example Usage

Example of using celebrate on a single POST route to validate req.body.

import express from 'express';
import BodyParser from 'body-parser';
import { celebrate, Joi, errors, Segments } from 'celebrate';

const app = express();
app.use(BodyParser.json());

app.post('/signup', celebrate({
  [Segments.BODY]: Joi.object().keys({
    name: Joi.string().required(),
    age: Joi.number().integer(),
    role: Joi.string().default('admin')
  }),
  [Segments.QUERY]: {
    token: Joi.string().token().required()
  }
}), (req, res) => {
  // At this point, req.body has been validated and 
  // req.body.role is equal to req.body.role if provided in the POST or set to 'admin' by joi
});
app.use(errors());

Example of using celebrate to validate all incoming requests to ensure the token header is present and matches the supplied regular expression.

import express from 'express';
import { celebrate, Joi, errors, Segments } from 'celebrate';
const app = express();

// validate all incoming request headers for the token header
// if missing or not the correct format, respond with an error
app.use(celebrate({
  [Segments.HEADERS]: Joi.object({
    token: Joi.string().required().regex(/abc\d{3}/)
  }).unknown()
}));
app.get('/', (req, res) => { res.send('hello world'); });
app.get('/foo', (req, res) => { res.send('a foo request'); });
app.use(errors());

API

celebrate does not have a default export. The following methods encompass the public API.

celebrate(schema, [joiOptions], [opts])

Returns a function with the middleware signature ((req, res, next)).

  • requestRules - an object where key can be one of the values from Segments and the value is a joi validation schema. Only the keys specified will be validated against the incoming request object. If you omit a key, that part of the req object will not be validated. A schema must contain at least one valid key.
  • [joiOpts] - optional object containing joi options that are passed directly into the validate function. Defaults to { warnings: true }.
  • [opts] - an optional object with the following keys. Defaults to {}.
    • reqContext - bool value that instructs joi to use the incoming req object as the context value during joi validation. If set, this will trump the value of joiOptions.context. This is useful if you want to validate part of the request object against another part of the request object. See the tests for more details.
    • mode - optional Modes for controlling the validation mode celebrate uses. Defaults to partial.

celebrator([opts], [joiOptions], schema)

This is a curried version of celebrate. It is curried with lodash.curryRight so it can be called in all the various fashions that API supports. Returns a function with the middleware signature ((req, res, next)).

  • [opts] - an optional object with the following keys. Defaults to {}.
    • reqContext - bool value that instructs joi to use the incoming req object as the context value during joi validation. If set, this will trump the value of joiOptions.context. This is useful if you want to validate part of the request object against another part of the request object. See the tests for more details.
    • mode - optional Modes for controlling the validation mode celebrate uses. Defaults to partial.
  • [joiOpts] - optional object containing joi options that are passed directly into the validate function. Defaults to { warnings: true }.
  • requestRules - an object where key can be one of the values from Segments and the value is a joi validation schema. Only the keys specified will be validated against the incoming request object. If you omit a key, that part of the req object will not be validated. A schema must contain at least one valid key.
Sample usage

This is an example use of curried celebrate in a real server.

  import express from 'express';
  import { celebrator, Joi, errors, Segments } from 'celebrate';
  const app = express();

  // now every instance of `celebrate` will use these same options so you only
  // need to do it once.
  const celebrate = celebrator({ reqContext: true }, { convert: true });

  // validate all incoming request headers for the token header
  // if missing or not the correct format, respond with an error
  app.use(celebrate({
    [Segments.HEADERS]: Joi.object({
      token: Joi.string().required().regex(/abc\d{3}/)
    }).unknown()
  }));
  app.get('/', celebrate({
    [Segments.HEADERS]: Joi.object({
      name: Joi.string().required()
    })
  }), (req, res) => { res.send('hello world'); });
  app.use(errors());

Here are some examples of other ways to call celebrator

  const opts = { reqContext: true };
  const joiOpts = { convert: true };
  const schema = {
    [Segments.HEADERS]: Joi.object({
      name: Joi.string().required()
    })
  };

  let c = celebrator(opts)(joiOpts)(schema);
  c = celebrator(opts, joiOpts)(schema);
  c = celebrator(opts)(joiOpts, schema);
  c = celebrator(opts, joiOpts, schema);

  // c would function the same in all of these cases.

errors([opts])

Returns a function with the error handler signature ((err, req, res, next)). This should be placed with any other error handling middleware to catch celebrate errors. If the incoming err object is an error originating from celebrate, errors() will respond a pre-build error object. Otherwise, it will call next(err) and will pass the error along and will need to be processed by another error handler.

  • [opts] - an optional object with the following keys
    • statusCode - number that will be used for the response status code in the event of an error. Must be greater than 399 and less than 600. It must also be a number available to the node HTTP module. Defaults to 400.
    • message - string that will be used for the message value sent out by the error handler. Defaults to 'Validation failed'

If the error response format does not suite your needs, you are encouraged to write your own and check isCelebrateError(err) to format celebrate errors to your liking.

Errors origintating from the celebrate() middleware are CelebrateError objects.

Joi

celebrate exports the version of joi it is using internally. For maximum compatibility, you should use this version when creating schemas used with celebrate.

Segments

An enum containing all the segments of req objects that celebrate can validate against.

{
  BODY: 'body',
  COOKIES: 'cookies',
  HEADERS: 'headers',
  PARAMS: 'params',
  QUERY: 'query',
  SIGNEDCOOKIES: 'signedCookies',
}

Modes

An enum containing all the available validation modes that celebrate can support.

  • PARTIAL - ends validation on the first failure. Does not apply joi transformations if any part of the request is invalid.
  • FULL - validates the entire request object and collects all the validation failures in the result. Does not apply joi transformations if any part of the request is invalid.
    • Note: In order for this to work, you will need to pass abortEarly: false to #joiOptions. Or to get the default behavior along with this, { abortEarly: false, warnings: true }

new CelebrateError([message], [opts])

Creates a new CelebrateError object. Extends the built in Error object.

  • message - optional string message. Defaults to 'Validation failed'.
  • [opts] - optional object with the following keys
    • celebrated - bool that, when true, adds Symbol('celebrated'): true to the result object. This indicates this error as originating from celebrate. You'd likely want to set this to true if you want the celebrate error handler to handle errors originating from the format function that you call in user-land code. Defaults to false.

CelebrateError has the following public properties:

  • details - a Map of all validation failures. The key is a Segments and the value is a joi validation error. Adding to details is done via details.set. The value must be a joi validation error or an exception will be thrown.
Sample usage
  const result = Joi.validate(req.params.id, Joi.string().valid('foo'), { abortEarly: false });
  const err = new CelebrateError(undefined, { celebrated: true });
  err.details.set(Segments.PARAMS, result.error);

isCelebrateError(err)

Returns true if the provided err object originated from the celebrate middleware, and false otherwise. Useful if you want to write your own error handler for celebrate errors.

  • err - an error object

Additional Details

Validation Order

celebrate validates request values in the following order:

  1. req.headers
  2. req.params
  3. req.query
  4. req.cookies (assuming cookie-parser is being used)
  5. req.signedCookies (assuming cookie-parser is being used)
  6. req.body (assuming body-parser is being used)

Mutation Warning

If you use any of joi's updating validation APIs (default, rename, etc.) celebrate will override the source value with the changes applied by joi (assuming the request is valid).

For example, if you validate req.query and have a default value in your joi schema, if the incoming req.query is missing a value for default, during validation celebrate will overwrite the original req.query with the result of joi.validate. This is done so that once req has been validated, you can be sure all the inputs are valid and ready to consume in your handler functions and you don't need to re-write all your handlers to look for the query values in res.locals.*.

Additional Info

According the the HTTP spec, GET requests should not include a body in the request payload. For that reason, celebrate does not validate the body on GET requests.

Issues

Before opening issues on this repo, make sure your joi schema is correct and working as you intended. The bulk of this code is just exposing the joi API as express middleware. All of the heavy lifting still happens inside joi. You can go here to verify your joi schema easily.