express-joi-validation
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) => {
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,
ValidatedRequest,
ValidatedRequestSchema,
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) => {
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 {
ValidatedRequest,
ValidatedRequestSchema,
createValidator
} from 'express-joi-validation'
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>
}
app.get(
'/hello',
validator.query(querySchema),
(req: ValidatedRequest<HelloRequestSchema>, res) => {
res.end(`Hello ${req.query.name}!`)
}
)
API
Structure
- module (express-joi-validation)
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
.
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()
});
app.use(validator.params(schema));
app.get('/orders/:id', (req, res, next) => {
});
app.get('/orders/:id', validator.params(schema), (req, res, next) => {
})
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
- 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({
passError: true
});
const app = require('express')();
const orders = require('lib/orders');
app.get('/orders', validator.query(require('./query-schema')), (req, res, next) => {
orders.getForQuery(req.query)
.then((listOfOrders) => res.json(listOfOrders))
.catch(next);
});
app.use((err, req, res, next) => {
if (err && err.error && err.error.isJoi) {
res.status(400).json({
type: err.type,
message: err.error.toString()
});
} else {
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) => {
if (err && err.type in ContainerTypes) {
const e: ExpressJoiError = err
res.status(400).end(`You submitted a bad ${e.type} paramater`)
} else {
res.status(500).end('internal server error')
}
})