JSON schema validator to use with @novice1/routing.
It provides a middleware that can validate req.params, req.body, req.query, req.headers, req.cookies and req.files against a schema using Ajv.
npm install @novice1/validator-json
// router.ts
import routing from '@novice1/routing'
import { validatorJson } from '@novice1/validator-json'
export default const router = routing()
/**
* Enable JSON validation on:
* - req.params
* - req.body
* - req.query
* - req.headers
* - req.cookies
* - req.files
*
* for that router
*/
router.setValidators(
validatorJson(
// ajv options
{ allErrors: true, keywords: ['meta', 'discriminator'] },
// middleware in case validation fails
function onerror(err, req, res, next) {
res.status(400).json(err)
}
)
)
Typescript interfaces are optional but convenient as they can help defining the JSON schema and be used in the controller.
import express from 'express'
import { JSONSchemaType } from 'ajv'
import router from './router'
// interface for "req.body"
interface BodyItem {
name: string
}
// JSON schema for "req.body"
const bodySchema: JSONSchemaType<BodyItem> = {
type: 'object',
properties: {
name: {
type: 'string',
minLength: 1
}
},
required: ['name'],
additionalProperties: false
}
router.post(
{
name: 'Post item',
path: '/items',
// the schema to validate
parameters: {
type: 'object',
properties: {
body: bodySchema
},
required: ['body'],
additionalProperties: true
},
// body parser
preValidators: express.json()
},
function (req: routing.Request<unknown, { name: string }, BodyItem>, res) {
res.json({ name: req.body.name })
}
)
// interface for "req.query"
interface GetQuery {
version?: string
}
// JSON schema for "req.query"
const querySchema: JSONSchemaType<GetQuery> = {
type: 'object',
properties: {
version: {
type: 'string',
description: 'version number',
enum: ['1','2','3'],
default: '2',
examples: ['2'],
nullable: true
}
}
}
router.get(
{
name: 'Main app',
path: '/app',
parameters: {
// the schema to validate
query: querySchema
}
},
function (req, res) {
res.send(req.query.version)
}
)
@sinclair/typeboximport express from 'express'
import { Type, Static } from '@sinclair/typebox'
import routing from '@novice1/routing'
import router from './router'
// schema for "req.body"
const bodySchema = Type.Object({
name: Type.String()
})
// type for "req.body"
type BodyItem = Static<typeof bodySchema>
router.post(
{
name: 'Post item',
path: '/items',
// the schema to validate
parameters: Type.Object({
body: bodySchema
}),
// body parser
preValidators: express.json()
},
function (req: routing.Request<unknown, { name: string }, BodyItem>, res) {
res.json({ name: req.body.name })
}
)
// schema for "req.query"
const querySchema = Type.Object({
version: Type.Optional(
Type.Union([
Type.Literal('1'),
Type.Literal('2'),
Type.Literal('3')
], {
type: 'string',
default: '2',
description: 'version number',
examples: ['2']
})
)
})
// type for "req.query"
type GetQuery = Static<typeof querySchema>
router.get(
{
name: 'Main app',
path: '/app',
parameters: {
// the schema to validate
query: querySchema
}
},
function (req: routing.Request<unknown, string, unknown, GetQuery>, res) {
res.send(req.query.version)
}
)
fluent-json-schemaimport express from 'express'
import { S } from 'fluent-json-schema'
import routing from '@novice1/routing'
import router from './router'
router.post(
{
name: 'Post item',
path: '/items',
parameters: S.object()
.prop('body', S.object().prop('name', S.string().required().minLength(1)))
.required()
.valueOf(),
// body parser
preValidators: express.json()
},
function (req, res) {
res.json({ name: req.body.name })
}
)
router.get(
{
name: 'Main app',
path: '/app',
parameters: {
query: S.object()
.prop(
'version',
S.string()
.description('version number')
.maxLength(1)
.enum(['1', '2', '3'])
.default('2')
.examples(['2'])
)
.valueOf()
}
},
function (req, res) {
res.send(req.query.version)
}
)
Override the validator's options and the error handler for a route.
import routing from '@novice1/routing'
import { validatorJson } from '@novice1/validator-json'
import Logger from '@novice1/logger'
import { Options } from 'ajv'
const router = routing()
router.setValidators(
validatorJson(
// default options
{ allErrors: true, keywords: ['meta', 'discriminator'] },
// default error handler
function onerror(err, req, res, next) {
res.status(400).json(err)
}
)
)
const onerror: routing.ErrorRequestHandler = (err, req, res) => {
res.status(400).json(err)
}
const validatorJsonOptions: Options = { logger: Logger, allErrors: false }
router.get(
{
path: '/override',
parameters: {
// overrides
onerror,
validatorJsonOptions
},
},
function (req, res) {
// ...
}
)
To keep your schemas "isolated" from other properties of parameters, you should define one property that will contain those schemas.
To do that you just need to initiate the validator with the name of the property:
router.setValidators(
validatorJson(
// ajv options
{},
// middleware in case validation fails
undefined,
// name of the property in 'parameters'
'schema'
)
)
Then in your routes you can do:
router.get(
{
name: 'Main app',
path: '/app',
// parameters
parameters: {
// property 'schema'
schema: {
query: {
type: 'object',
properties: {
version: {
type: 'string',
description: 'version number',
enum: ['1','2','3'],
default: '2',
examples: ['2'],
nullable: true
}
}
}
}
}
},
function (req, res) {
res.json(req.query.version)
}
)