Joi middleware to valdiate your Express.js routes

Let’s create ourselves router validation that is easy to lookup, easy to modify.

// routes/auth.js
/**
 * @api {post} /api/auth/login
 * @api AuthLogin
 * @apiParam {string} email
 * @apiParam {string} password
 *
 * Create new user session
 */
router.post(
  "/login",
  middleware.validateBody(
    Joi.object().keys({
      email: schemas.email.required(),
      password: schemas.password.required(),
    }),
  ),
  authHandlers.login,
);

Our file tree:

src/
 | routes/
 | middleware.js
 | schemas.js

Dependencies

First off install Joi:

npm install --save @hapi/joi

Middleware

I like to start with a middleware.js file. We fill create one function for each type of request validation - request body, url query params (url.query), url parameters url.params.

// /routes/middleware.js
/**
 * Validate request contents against a joi schema.
 * Returns http 422 if validation fails.
 * @param {object} schema - Joi schema
 * @param {object} data
 * @param {object} res
 * @param {function} next
 */
function validate(schema, data, res, next) {
  const validation = schema.validate(data);

  if (validation.error) {
    res.status(422).json({
      error: {
        message: validation.error.details[0].message,
        field: validation.error.details[0].context.key,
      },
    });
    return;
  }

  next();
}

/**
 * Validate request body
 * @param {object} schema - Joi schema
 */
function validateBody(schema) {
  return function validateWrapper(req, res, next) {
    return validate(schema, req.body, res, next);
  };
}

/**
 * Validate GET request query
 * @param {object} schema - Joi schema
 */
function validateQuery(schema) {
  return function validateWrapper(req, res, next) {
    return validate(schema, req.query, res, next);
  };
}

/**
 * Validate GET request parameters
 * @param {object} schema - Joi schema
 */
function validateParams(schema) {
  return function validateWrapper(req, res, next) {
    return validate(schema, req.params, res, next);
  };
}

module.exports = {
  validateBody,
  validateQuery,
  validateParams,
};

Schemas

Now let’s write our Joi schemas. Let’s create a shared file where we define all our schemas. Why not write them inline? Imagine you have a subscription interval validation schema, you want this to be consistent across all endpoints.

// schemas.js
module.exports.email = Joi.string().email();
module.exports.password = Joi.string().min(8);
module.exports.subscriptionInterval = Joi.string().valid(["30", "90"]);
...

If this file grew too large I would split it up by domain into multiple files in a schemas/ folder.