Validación & Sanitización
Validation checks if an input meets a set of criteria (such as the value of a property is a string).
Sanitization modifies the input to ensure that it is valid (such as coercing a type).
Foal offers several utils and hooks to handle both validation and sanitization. They are particularly useful for checking and transforming parts of HTTP requests (such as the body).
With a JSON Schema (AJV)
Ajv, the JSON Schema Validator
FoalTS default validation and sanitization system is based on AJV version 6, a fast JSON Schema Validator. You'll find more details on how to define a shema on its website.
Options
Here is the list of AJV options that can be overridden with FoalTS configuration system.
Ajv option | Configuration key | FoalTS default |
---|---|---|
$data | settings.ajv.$data | - |
allErrors | settings.ajv.allErrors | - |
coerceTypes | settings.ajv.coerceType | true |
nullable | settings.ajv.nullable | - |
removeAdditional | settings.ajv.removeAdditional | true |
useDefaults | settings.ajv.useDefaults | true |
Example
- YAML
- JSON
- JS
settings:
ajv:
coerceTypes: true
{
"settings": {
"ajv": {
"coerceTypes": true
}
}
}
module.exports = {
settings: {
ajv: {
coerceTypes: true
}
}
}
Validation & Sanitization of HTTP Requests
FoalTS provides many hooks to validate and sanitize HTTP requests. When validation fails, they return an HttpResponseBadRequest
object whose body contains the validation errors.
Example
import { Context, HttpResponseOK, Post, ValidateBody } from '@foal/core';
export class MyController {
@Post('/user')
@ValidateBody({
additionalProperties: false,
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
},
required: [ 'firstName', 'lastName' ],
type: 'object'
})
postUser(ctx: Context) {
// In this method we are sure that firstName and lastName
// are defined thanks to the above hook.
console.log(
ctx.request.body.firstName, ctx.request.body.lastName
);
return new HttpResponseOK();
}
}
ValidateBody
It validates the request body (Context.request.body
).
HTTP request
POST /products
{
"price": "hello world"
}
Controller (first example)
import { Post, ValidateBody } from '@foal/core';
export class AppController {
@Post('/products')
@ValidateBody({
additionalProperties: false,
properties: {
price: { type: 'integer' },
},
required: [ 'price' ],
type: 'object'
})
createProduct() {
// ...
}
}
Controller (second example)
import { Post, ValidateBody } from '@foal/core';
export class AppController {
schema = {
additionalProperties: false,
properties: {
price: { type: 'integer' },
},
required: [ 'price' ],
type: 'object'
};
@Post('/products')
@ValidateBody(controller => controller.schema)
createProduct() {
// ...
}
}
HTTP response (400 - BAD REQUEST)
{
"body": [
{
"dataPath": ".price",
"keyword": "type",
"message": "should be integer",
"params": {
"type": "integer"
},
"schemaPath": "#/properties/price/type"
}
]
}
ValidateHeader
It validates the request headers (Context.request.headers
).
HTTP request
GET /products
Authorization: xxx
A-Number: hello
Controller (first example)
import { Post, ValidateHeader } from '@foal/core';
export class AppController {
@Get('/products')
@ValidateHeader('Authorization')
@ValidateHeader('A-Number', { type: 'integer' }, { required: false })
readProducts() {
// ...
}
}
Controller (second example)
import { Post, ValidateHeader } from '@foal/core';
export class AppController {
schema = { type: 'integer' };
@Get('/products')
@ValidateHeader('Authorization')
@ValidateHeader('A-Number', c => c.schema, { required: false })
readProducts() {
// ...
}
}
HTTP response (400 - BAD REQUEST)
{
"headers": [
{
"dataPath:" "['a-number']",
"keyword": "type",
"message": "should be integer",
"params": {
"type": "integer"
},
"schemaPath": "#/properties/a-number/type"
}
]
}
ValidateCookie
It validates the request cookies (Context.request.cookies
).
HTTP request
GET /products
Cookies: Authorization=xxx; A-Number=hello
Controller (first example)
import { Post, ValidateCookie } from '@foal/core';
export class AppController {
@Get('/products')
@ValidateCookie('Authorization')
@ValidateCookie('A-Number', { type: 'integer' }, { required: false })
readProducts() {
// ...
}
}
Controller (second example)
import { Post, ValidateCookie } from '@foal/core';
export class AppController {
schema = { type: 'integer' };
@Get('/products')
@ValidateCookie('Authorization')
@ValidateCookie('A-Number', c => c.schema, { required: false })
readProducts() {
// ...
}
}
HTTP response (400 - BAD REQUEST)
{
"cookies": [
{
"dataPath": "['a-number']",
"keyword": "type",
"message": "should be integer",
"params": {
"type": "integer"
},
"schemaPath": "#/properties/a-number/type"
}
]
}
ValidatePathParam
It validates the request path parameter (Context.request.params
).
HTTP request
GET /products/xxx
Controller (first example)
import { Post, ValidatePathParam } from '@foal/core';
export class AppController {
@Get('/products/:productId')
@ValidatePathParam('productId', { type: 'integer' })
readProducts() {
// ...
}
}
Controller (second example)
import { Post, ValidatePathParam } from '@foal/core';
export class AppController {
schema = { type: 'integer' };
@Get('/products/:productId')
@ValidatePathParam('productId', c => c.schema)
readProducts() {
// ...
}
}