Published:

Node.js + Express API - Request Schema Validation with Joi

This is a quick example of how to validate HTTP request data using the joi schema validation library in a Node.js + Express API.

For more info about joi schema validation see https://www.npmjs.com/package/joi.


Installing joi from npm

With the npm CLI: npm install joi

With the yarn CLI: yarn add joi


Schema validation middleware for create account requests in Node.js with joi

This example Node.js middleware function validates a request to create a new account. It defines schema rules with the joi schema validation library and calls schema.validate(req.body, options) to determine if the request is valid.

  • Joi.string() validates that all properties are strings
  • Joi.required() makes all properties required
  • Joi.email() validates that the email property contains a valid email address
  • Joi.valid(Joi.ref('password')) validates that the confirm password property matches the password property
  • Joi.valid('Admin', 'User') validates that the role property matches one of the specified roles ('Admin' or 'User')
function createAccountSchema(req, res, next) {
    // create schema object
    const schema = Joi.object({
        title: Joi.string().required(),
        firstName: Joi.string().required(),
        lastName: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().min(6).required(),
        confirmPassword: Joi.string().valid(Joi.ref('password')).required(),
        role: Joi.string().valid('Admin', 'User').required()
    });

    // schema options
    const options = {
        abortEarly: false, // include all errors
        allowUnknown: true, // ignore unknown props
        stripUnknown: true // remove unknown props
    };

    // validate request body against schema
    const { error, value } = schema.validate(req.body, options);
    
    if (error) {
        // on fail return comma separated errors
        next(`Validation error: ${error.details.map(x => x.message).join(', ')}`);
    } else {
        // on success replace req.body with validated value and trigger next middleware function
        req.body = value;
        next();
    }
}


Schema validation middleware for update account requests in Node.js with joi

This example Node.js middleware function validates a request to update an existing account.

It's similar to the create account schema above with a few small differences:

  • The schema rules are created in a separate object before the schema object to allow for a conditional rule to be added only for admin users (schemaRules.role)
  • All properties are optional because none have the Joi.required() rule
  • Joi.empty('') tells joi to treat empty strings ('') as undefined so any property containing an empty string will be omitted from the value returned by schema.validate(req.body, options)
  • .with('password', 'confirmPassword') makes the confirmPassword property required only if the password property has a value, otherwise it is optional
function updateAccountSchema(req, res, next) {
    // define base schema rules
    const schemaRules = {
        title: Joi.string().empty(''),
        firstName: Joi.string().empty(''),
        lastName: Joi.string().empty(''),
        email: Joi.string().email().empty(''),
        password: Joi.string().min(6).empty(''),
        confirmPassword: Joi.string().valid(Joi.ref('password')).empty('')
    };

    // conditional schema rule - only admins can update role
    if (req.user.role === 'Admin') {
        schemaRules.role = Joi.string().valid('Admin', 'User').empty('');
    }

    // create schema object with rules
    const schema = Joi.object(schemaRules)
        // make confirmPassword required IF password is present
        .with('password', 'confirmPassword');

    // schema options
    const options = {
        abortEarly: false, // include all errors
        allowUnknown: true, // ignore unknown props
        stripUnknown: true // remove unknown props
    };

    // validate request body against schema
    const { error, value } = schema.validate(req.body, options);
    
    if (error) {
        // on fail return comma separated errors
        next(`Validation error: ${error.details.map(x => x.message).join(', ')}`);
    } else {
        // on success replace req.body with validated value and trigger next middleware function
        req.body = value;
        next();
    }
}


Wrapping it up in a Node.js + Express controller

This example Node.js controller shows how the above schema validation middleware can be hooked up to routes with the Express router.

There are two routes defined:

  • The create account route listens for POST requests to the /accounts/ path and is handled by the middleware functions createAccountSchema followed by createAccount
  • The update account route listens for PUT requests to the /accounts/:id path and is handled by the middleware functions updateAccountSchema followed by updateAccount

The schema validation functions have been refactored and the common pieces have been moved into the validateRequest() helper function, so the validation functions only contain schema rules for each route now.

The controller is a simplified version of the accounts controller from a boilerplate api project I posted recently, for more info and to test it in a fully functioning project see Node + Mongo - Boilerplate API with Email Sign Up, Verification, Authentication & Forgot Password.

const express = require('express');
const router = express.Router();
const Joi = require('joi');
const accountService = require('./account.service');

// routes
router.post('/accounts/', createAccountSchema, createAccount);
router.put('/accounts/:id', updateAccountSchema, updateAccount);

module.exports = router;

function createAccountSchema(req, res, next) {
    const schema = Joi.object({
        title: Joi.string().required(),
        firstName: Joi.string().required(),
        lastName: Joi.string().required(),
        email: Joi.string().email().required(),
        password: Joi.string().min(6).required(),
        confirmPassword: Joi.string().valid(Joi.ref('password')).required(),
        role: Joi.string().valid('Admin', 'User').required()
    });
    validateRequest(req, next, schema);
}

function createAccount(req, res, next) {
    accountService.create(req.body)
        .then(account => res.json(account))
        .catch(next);
}

function updateAccountSchema(req, res, next) {
    const schemaRules = {
        title: Joi.string().empty(''),
        firstName: Joi.string().empty(''),
        lastName: Joi.string().empty(''),
        email: Joi.string().email().empty(''),
        password: Joi.string().min(6).empty(''),
        confirmPassword: Joi.string().valid(Joi.ref('password')).empty('')
    };

    // only admins can update role
    if (req.user.role === 'Admin') {
        schemaRules.role = Joi.string().valid('Admin', 'User').empty('');
    }

    const schema = Joi.object(schemaRules).with('password', 'confirmPassword');
    validateRequest(req, next, schema);
}

function updateAccount(req, res, next) {
    accountService.update(req.params.id, req.body)
        .then(account => res.json(account))
        .catch(next);
}

// helper functions

function validateRequest(req, next, schema) {
    const options = {
        abortEarly: false, // include all errors
        allowUnknown: true, // ignore unknown props
        stripUnknown: true // remove unknown props
    };
    const { error, value } = schema.validate(req.body, options);
    if (error) {
        next(`Validation error: ${error.details.map(x => x.message).join(', ')}`);
    } else {
        req.body = value;
        next();
    }
}

 

Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter or GitHub to be notified when I post new content.

 


Supported by