Menu Close

express-joi-validation

express-joi-validation

TravisCI
Coverage Status
npm version
TypeScript
npm downloads
Known Vulnerabilities

A middleware for validating express inputs using Joi schemas. Features include:

  • TypeScript support.
  • Specify the order in which request inputs are validated.
  • Replaces the incoming req.body, req.query, etc and with the validated result
  • Retains the original req.body inside a new property named req.originalBody.
    . The same applies for headers, query, and params using the original prefix,
    e.g req.originalQuery
  • Chooses sensible default Joi options for headers, params, query, and body.
  • Uses peerDependencies to get a Joi instance of your choosing instead of
    using a fixed version.

Quick Links

Install

You need to install joi with this module since it relies on it in
peerDependencies.

npm i express-joi-validation joi --save

Example

A JavaScript and TypeScript example can be found in the example/ folder of
this repository.

Usage (JavaScript)

const Joi = require('joi') const app = require('express')() const validator = require('express-joi-validation').createValidator({}) const querySchema = Joi.object({ name: Joi.string().required() }) app.get('/orders', validator.query(querySchema), (req, res) => { // If we're in here then the query was valid! res.end(`Hello ${req.query.name}!`) })

Usage (TypeScript)

For TypeScript a helper ValidatedRequest and
ValidatedRequestWithRawInputsAndFields type is provided. This extends the
express.Request type and allows you to pass a schema using generics to
ensure type safety in your handler function.

import * as Joi from 'joi' import * as express from 'express' import { ContainerTypes, // Use this as a replacement for express.Request ValidatedRequest, // Extend from this to define a valid schema type/interface ValidatedRequestSchema, // Creates a validator that generates middlewares createValidator } from 'express-joi-validation' const app = express() const validator = createValidator() const querySchema = Joi.object({ name: Joi.string().required() }) interface HelloRequestSchema extends ValidatedRequestSchema { [ContainerTypes.Query]: { name: string } } app.get( '/hello', validator.query(querySchema), (req: ValidatedRequest<HelloRequestSchema>, res) => { // Woohoo, type safety and intellisense for req.query! res.end(`Hello ${req.query.name}!`) } )

You can minimise some duplication by using joi-extract-type.

NOTE: this does not work with Joi v16+ at the moment. See this issue.

import * as Joi from 'joi' import * as express from 'express' import { // Use this as a replacement for express.Request ValidatedRequest, // Extend from this to define a valid schema type/interface ValidatedRequestSchema, // Creates a validator that generates middlewares createValidator } from 'express-joi-validation' // This is optional, but without it you need to manually generate // a type or interface for ValidatedRequestSchema members import 'joi-extract-type' const app = express() const validator = createValidator() const querySchema = Joi.object({ name: Joi.string().required() }) interface HelloRequestSchema extends ValidatedRequestSchema { [ContainerTypes.Query]: Joi.extractType<typeof querySchema> // Without Joi.extractType you would do this: // query: { // name: string // } } app.get( '/hello', validator.query(querySchema), (req: ValidatedRequest<HelloRequestSchema>, res) => { // Woohoo, type safety and intellisense for req.query! res.end(`Hello ${req.query.name}!`) } )

API

Structure

createValidator(config)

Creates a validator. Supports the following options:

  • passError (default: false) – Passes validation errors to the express error
    hander using next(err) when true
  • statusCode (default: 400) – The status code used when validation fails and
    passError is false.

validator.query(schema, [options])

Creates a middleware instance that will validate the req.query for an
incoming request. Can be passed options that override the config passed
when the validator was created.

Supported options are:

  • joi – Custom options to pass to Joi.validate.
  • passError – Same as above.
  • statusCode – Same as above.

validator.body(schema, [options])

Creates a middleware instance that will validate the req.body for an incoming
request. Can be passed options that override the options passed when the
validator was created.

Supported options are the same as validator.query.

validator.headers(schema, [options])

Creates a middleware instance that will validate the req.headers for an
incoming request. Can be passed options that override the options passed
when the validator was created.

Supported options are the same as validator.query.

validator.params(schema, [options])

Creates a middleware instance that will validate the req.params for an
incoming request. Can be passed options that override the options passed
when the validator was created.

Supported options are the same as validator.query.

validator.response(schema, [options])

Creates a middleware instance that will validate the outgoing response.
Can be passed options that override the options passed when the instance was
created.

Supported options are the same as validator.query.

validator.fields(schema, [options])

Creates a middleware instance that will validate the fields for an incoming
request. This is designed for use with express-formidable. Can be passed
options that override the options passed when the validator was created.

The instance.params middleware is a little different to the others. It must
be attached directly to the route it is related to. Here’s a sample:

const schema = Joi.object({ id: Joi.number().integer().required() }); // INCORRECT app.use(validator.params(schema)); app.get('/orders/:id', (req, res, next) => { // The "id" parameter will NOT have been validated here! }); // CORRECT app.get('/orders/:id', validator.params(schema), (req, res, next) => { // This WILL have a validated "id" })

Supported options are the same as validator.query.

Behaviours

Joi Versioning

This module uses peerDependencies for the Joi version being used.
This means whatever joi version is in the dependencies of your
package.json will be used by this module.

Validation Ordering

Validation can be performed in a specific order using standard express
middleware behaviour. Pass the middleware in the desired order.

Here’s an example where the order is headers, body, query:

route.get(
  '/tickets',
  validator.headers(headerSchema),
  validator.body(bodySchema),
  validator.query(querySchema),
  routeHandler
);

Error Handling

When validation fails, this module will default to returning a HTTP 400 with
the Joi validation error as a text/plain response type.

A passError option is supported to override this behaviour. This option
forces the middleware to pass the error to the express error handler using the
standard next function behaviour.

See the Custom Express Error Handler section
for an example.

Joi Options

It is possible to pass specific Joi options to each validator like so:

route.get(
  '/tickets',
  validator.headers(
    headerSchema,
    {
      joi: {convert: true, allowUnknown: true}
    }
  ),
  validator.body(
    bodySchema,
    {
      joi: {convert: true, allowUnknown: false}
    }
  )
  routeHandler
);

The following sensible defaults for Joi are applied if none are passed:

Query

  • convert: true
  • allowUnknown: false
  • abortEarly: false

Body

  • convert: true
  • allowUnknown: false
  • abortEarly: false

Headers

  • convert: true
  • allowUnknown: true
  • stripUnknown: false
  • abortEarly: false

Route Params

  • convert: true
  • allowUnknown: false
  • abortEarly: false

Fields (with express-formidable)

  • convert: true
  • allowUnknown: false
  • abortEarly: false

Custom Express Error Handler

const validator = require('express-joi-validation').createValidator({ // This options forces validation to pass any errors the express // error handler instead of generating a 400 error passError: true }); const app = require('express')(); const orders = require('lib/orders'); app.get('/orders', validator.query(require('./query-schema')), (req, res, next) => { // if we're in here then the query was valid! orders.getForQuery(req.query) .then((listOfOrders) => res.json(listOfOrders)) .catch(next); }); // After your routes add a standard express error handler. This will be passed the Joi // error, plus an extra "type" field so we can tell what type of validation failed app.use((err, req, res, next) => { if (err && err.error && err.error.isJoi) { // we had a joi error, let's return a custom 400 json response res.status(400).json({ type: err.type, // will be "query" here, but could be "headers", "body", or "params" message: err.error.toString() }); } else { // pass on to another error handler next(err); } });

In TypeScript environments err.type can be verified against the exported
ContainerTypes:

import { ContainerTypes } from 'express-joi-validation' app.use((err: any|ExpressJoiError, req: express.Request, res: express.Response, next: express.NextFunction) => { // ContainerTypes is an enum exported by this module. It contains strings // such as "body", "headers", "query"... if (err && err.type in ContainerTypes) { const e: ExpressJoiError = err // e.g "you submitted a bad query paramater" res.status(400).end(`You submitted a bad ${e.type} paramater`) } else { res.status(500).end('internal server error') } })
View Source Code
Posted in Development