Introduction

AdonisJS has first-class support for parsing and validating the request body, and there is no need to install any 3rd party packages for the same. Instead, define the validation schema and validate the request body against it.

import Route from '@ioc:Adonis/Core/Route'
import { schema } from '@ioc:Adonis/Core/Validator'
Route.post('posts', async ({ request }) => {
/**
* Schema definition
*/
const newPostSchema = schema.create({
title: schema.string({ trim: true }),
body: schema.string({ escape: true }),
categories: schema.array().members(schema.number()),
})
/**
* Validate request body against the schema
*/
const payload = await request.validate({ schema: newPostSchema })
})

The validator also extracts the static types from the schema definition. Meaning, you get the runtime validations along with the static type safety from a single schema definition.

Schema composition

The schema definition is divided into three main parts.

  • The schema.create method defines the shape of the data you expect.
  • The schema.string, schema.number, and other similar methods define the data type for an individual field.
  • Finally, you use the rules object to apply additional validation constraints on a given field. For example: Validating a string to be a valid email and is unique inside the database.

The rules object is imported from @ioc:Adonis/Core/Validator

import { schema, rules } from '@ioc:Adonis/Core/Validator'

If you look carefully, we have separated the format validations from core data types. So, for example, there is no data type called schema.email. Instead, we use the rules.email method to ensure a string is formatted as an email.

This separation helps extend the validator with custom rules without creating unnecessary schema types that have no meaning. For example, there is no thing called email type; it is just a string, formatted as an email.

Marking fields as optional

The schema properties are required by default. However, you can mark them as optional by chaining the optional method. The optional variant is available for all the schema types.

schema.create({
username: schema.string.optional(),
password: schema.string.optional()
})

Validating nested objects/arrays

You can validate nested objects and arrays using the schema.array and schema.object methods.

schema.create({
user: schema
.object()
.members({
username: schema.string(),
}),
tags: schema
.array()
.members(schema.string())
})

Validating HTTP requests

You can validate the request body, query string and route parameters for a given HTTP request using the request.validate method. In case of a failure, the validate method will raise an exception.

import Route from '@ioc:Adonis/Core/Route'
import { schema } from '@ioc:Adonis/Core/Validator'
Route.post('users', async ({ request, response }) => {
const newUserSchema = schema.create({
params: schema
.object()
.members({
// ... define schema for your route parameters
})
// ... define schema for your body and query string
})
try {
const payload = await request.validate({
schema: newUserSchema
})
} catch (error) {
response.badRequest(error.messages)
}
})

We recommend NOT self-handling the exception and let AdonisJS convert the exception to a response using content negotiation.

Following is an explanation of how content negotiation works.

Server rendered app

If you build a standard web application with server-side templating, we will redirect the client back to the form and pass the errors as session flash messages.

Following is the structure of error messages inside the session's flash store.

{
errors: {
username: ['username is required']
}
}

You can access them using the flashMessages global helper.

@if(flashMessages.has('errors.username'))
<p> {{ flashMessages.get('errors.username') }} </p>
@end

Requests with Accept=application/json header

Requests negotiating for the JSON data type receive the error messages as an array of objects. Each error message contains the field name, the failed validation rule, and the error message.

{
errors: [
{
field: 'title',
rule: 'required',
message: 'required validation failed',
},
]
}

JSON API

Requests negotiating using Accept=application/vnd.api+json header, receives the error messages as per the JSON API spec .

{
errors: [
{
code: 'required',
source: {
pointer: 'title',
},
title: 'required validation failed'
}
]
}

Standalone validator usage

You can also use the validator outside of an HTTP request by importing the validate method from the Validator module. The functional API remains the same. However, you will have to provide the data to validate manually.

import { validator, schema } from '@ioc:Adonis/Core/Validator'
await validator.validate({
schema: schema.create({
// ... define schema
}),
data: {
email: 'virk@adonisjs.com',
password: 'secret'
}
})

Also, since you perform the validation outside of an HTTP request, you will have to handle the exception and display the errors manually.

Validator classes

Validator classes allow you to extract the inline schema from your controllers and move them to a dedicated class.

You can create a new validator by executing the following ace command.

node ace make:validator CreateUser
# CREATE: app/Validators/CreateUserValidator.ts

All the validation related properties, including the schema, messages are defined as properties on the class.

app/Validators/CreateUserValidator.ts
import { schema } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class CreateUserValidator {
constructor (protected ctx: HttpContextContract) {
}
public schema = schema.create({
})
public messages = {}
}

Using validator

Instead of passing an object with the schema property, you can now pass the class constructor to the request.validate method.

import Route from '@ioc:Adonis/Core/Route'
import CreateUser from 'App/Validators/CreateUserValidator'
Route.post('users', async ({ request, response }) => {
await request.validate(CreateUser)
})

During validation, a new instance of the validator class is created behind the scenes. Also, the request.validate method will pass the current HTTP context as a first constructor argument.

You can also manually construct the class instance and pass any arguments you like. For example:

Route.post('users', async ({ request, response }) => {
await request.validate(
new CreateUser({
countries: fetchAllowedCountries(),
states: fetchAllowedStates()
})
)
})

Following is an example of using the validator classes outside of the HTTP request.

import { validator } from '@ioc:Adonis/Core/Validator'
import CreateUser from 'App/Validators/CreateUserValidator'
await validator.validate(
new CreateUser({
countries: fetchAllowedCountries(),
states: fetchAllowedStates()
})
)

What's next?