A middleware for validating Koa2 inputs using Joi schemas. Fills some of the voids I found that other Joi middleware miss such as:
- Allow the developers to easily specify the order in which request inputs are validated.
- Replaces the incoming
contenxt.request.body
and others with converted Joi values. The same applies for headers, query, and params, but... - Retains the original
contenxt.request.body
inside a new property namedcontenxt.request.originalBody
. The same applies for headers, query, and params using theoriginal
prefix, e.gcontenxt.request.originalQuery
will contain thecontenxt.request.query
as it looked before validation. - Passes sensible default options to Joi for headers, params, query, and body. These are detailed below.
- Uses
peerDependencies
to get a Joi instance of your choosing instead of using a fixed version.
- headers -
contenxt.request.headers
- body -
contenxt.request.body
- query -
contenxt.request.query
- params -
contenxt.params
- headers -
contenxt.request.originalHeaders
- body -
contenxt.request.originalBody
- query -
contenxt.request.originalQuery
- params -
contenxt.originalParams
You need to install joi
along with this module for it to work since it relies
on it as a peer dependency. Currently this module has only been tested with joi
version 10.0 and higher.
# we install our middleware AND joi since it's required by our middleware
npm i koa2-joi-validate joi --save
# OR
yarn add koa2-joi-validate joi
const Joi = require('@hapi/joi');
const Koa = require('koa');
const app = new Koa();
const Router = require('@koa/router');
const validator = require('koa2-joi-validate')();
const querySchema = Joi.object({
type: Joi.string().required().valid('food', 'drinks', 'entertainment')
})
const router = new Router();
router.post('/test/:type',
validator.query(querySchema),
(ctx, next) => {
console.log(`original query ${JSON.stringify(ctx.request.originalQuery)} vs.
the sanatised query ${JSON.stringify(ctx.request.query)}`);
}
);
app.use(router.routes());
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.
If you'd like to validate different request inputs in differing orders it's simple, just define the the middleware in the order desired.
Here's an example where we do headers, body, and finally the query:
// verify headers, then body, then query
router.get(
'/tickets',
validator.headers(headerSchema),
validator.body(bodySchema),
validator.query(querySchema),
validator.params(paramsSchema),
routeHandler
);
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, and force the
middleware to pass the error to the koa2 error handler you've defined.
It is possible to pass specific Joi options to each validator like so:
router.get(
'/tickets',
validator.headers(
headerSchema,
{
joi: {convert: true, allowUnknown: true}
}
),
validator.body(
bodySchema,
{
joi: {convert: true, allowUnknown: false}
}
),
routeHandler
);
The following sensible defaults are applied if you pass none:
- convert: true (But Koa2 always convert number to string.)
- allowUnknown: false
- abortEarly: false
- convert: true
- allowUnknown: false
- abortEarly: false
- convert: true
- allowUnknown: true
- stripUnknown: false
- abortEarly: false
- convert: true
- allowUnknown: false
- abortEarly: false
If you don't like the default error format returned by this module you can override it like so:
const validator = require('koa2-joi-validate')({
passError: true // NOTE: this tells the module to pass the error along for you
});
const Koa = require('koa');
const app = new Koa();
const Router = require('@koa/router');
const router = new Router();
// Before your routes add a standard Koa2 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(async (ctx, next) => next().catch((err) => {
if (err.message && err.message.isJoi) {
ctx.status = 400;
ctx.body = {
type: err.message.type,
message: err.message.details.toString()
}
} else {
ctx.status = 500;
ctx.body = 'Internal Server Error';
}
}));
router.get('/orders',
validator.query(require('./query-schema')),
(ctx, next) => {
#YOUR MIDDLEWARE
}
);
app.use(router.routes());
A factory function an instance of the module for use. Can pass the following options:
- passError - Set this to true if you'd like validation errors to get passed to the Koa2 error handler so you can handle them manually vs. the default behaviour that returns a 400.
- statusCode - The status code to use when validation fails and passError is false. Default is 400.
Each instance function can be passed an options Object with the following:
- joi - Custom options to pass to
Joi.validate
. - passError - Same as above.
- statusCode - Same as above.
Create a middleware instance that will validate the query for an incoming
request. Can be passed options
that override the options passed when the
instance was created.
Create a middleware instance that will validate the body for an incoming
request. Can be passed options
that override the options passed when the
instance was created.
Create a middleware instance that will validate the headers for an incoming
request. Can be passed options
that override the options passed when the
instance was created.
Create a middleware instance that will validate the params for an incoming
request. Can be passed options
that override the options passed when the
instance was created.
Create a middleware instance that will validate the outgoing response.
Can be passed options
that override the options passed when the instance 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()
});