adonis install @adonisjs/validator
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
AdonisJs makes it so simple to validate the user’s input with the help of dedicated validation provider. In this guide you learn how to validate the data manually or via route validators.
Adonisjs validator using indicative under the hood, so make sure to read the official documentation whenever required. |
Follow the below instructions to setup the validation provider.
adonis install @adonisjs/validator
Next, you need to register the provider inside start/app.js
file.
const providers = [
'@adonisjs/validator/providers/ValidatorProvider'
]
That is all 🎉
Let’s start with the example of validating user input received via HTML form.
<form method="POST" action="{{ route('UserController.store') }}">
<div>
<input type="text" name="email" />
</div>
<div>
<input type="text" name="password" />
</div>
<button type="submit"> Submit </button>
</form>
Next, register the route and controller to handle the form submission and use the validator to validate the data.
Route.post('users', 'UserController.store')
const { validate } = use('Validator')
class UserController {
async store ({ request, session, response }) {
const rules = {
email: 'required|email|unique:users,email',
password: 'required'
}
const validation = await validate(request.all(), rules)
if (validation.fails()) {
session
.withErrors(validation.messages())
.flashExcept(['password'])
return response.redirect('back')
}
return 'Validation passed'
}
}
module.exports = UserController
Let’s understand the above piece of code in small steps.
First, we have defined the rules
schema.
Next, we make use of the validate
method to validate the request data with our defined rules.
If validation fails, we flash all the errors and redirect the user back to the form.
Quickly modify the HTML form to display the flash messages, which are set when validation fails.
<form method="POST" action="{{ route('UserController.store') }}">
<div>
<input type="text" name="email" value="{{ old('email', '') }}" />
{{ elIf('<span>$self</span>', getErrorFor('email'), hasErrorFor('email')) }}
</div>
<div>
<input type="text" name="password" />
{{ elIf('<span>$self</span>', getErrorFor('password'), hasErrorFor('password')) }}
</div>
<button type="submit"> Submit </button>
</form>
Below is the list of available methods.
Validate data with defined rules. Optionally, you can define custom error messages.
const { validate } = use('Validator')
const validation = await validate(data, rules)
if (validation.fails()) {
return validation.messages()
}
Same as validate
but validate’s all the fields, whereas the validate method stops on first error.
const { validateAll } = use('Validator')
const validation = await validateAll(data, rules)
This method returns a new object with sanitized data.
const { sanitize } = use('Validator')
const data = sanitize(request.all(), rules)
Returns a reference to indicative raw validator.
const { is } = use('Validator')
if (is.email('foo@bar.com')) {
}
Returns a reference to indicative raw sanitizor.
const { sanitizor } = use('Validator')
const slug = sanitizor.slug('My first blog post')
Returns a reference to formatters
const { formatters } = use('Validator')
formatters.default('jsonapi')
// adding new formatter
formatters.register('custom', CustomImplementation)
Majority of times the data validation happens during the normal HTTP request/response lifecycle, where you end up writing the same amount of code inside each controller.
The route validator makes the process of manual validation a bit easier, by defining Route validator.
// For a single route
Route
.post('users', 'UserController.store')
.validator('StoreUser')
// For a resourceful route
Route
.resource('users', 'UserController')
.validator(new Map([
[['users.store'], ['StoreUser']],
[['users.update'], ['UpdateUser']]
]))
All validators live inside app/Validators
directory. Let’s create the StoreUser
validator by using the adonis
command.
adonis make:validator StoreUser
Output
create: app/Validators/StoreUser.js
All we need to do is, define the rules on the validator
'use strict'
class StoreUser {
get rules () {
return {
email: 'required|email|unique:users',
password: 'required'
}
}
}
module.exports = StoreUser
That is all! If the validation fails, the validator automatically set the errors as flash messages and redirects the user to the form.
Whereas, it sends back the JSON response if request has Accept: application/json
header set
Also, you can sanitize the user input by defining the sanitizationRules
. The sanitization happens before the validation.
class StoreUser {
get sanitizationRules () {
return {
email: 'normalize_email',
age: 'to_int'
}
}
}
module.exports = StoreUser
Since every application is structured differently, automatic failure handling may be not something you want. You can handle failures yourself by defining fails
method on validator class.
class StoreUser {
async fails (errorMessages) {
return this.ctx.response.send(errorMessages)
}
}
module.exports = StoreUser
At times you want to validate custom properties, which are not part of request body. For example validating some headers. Same can be done by defining data
property on the validator instance.
class StoreUser {
get rules () {
return {
sessionId: 'required'
}
}
get data () {
const requestBody = this.ctx.request.all()
const sessionId = this.ctx.request.header('X-Session-Id')
return Object.assign({}, requestBody, { sessionId })
}
}
module.exports = StoreUser
Just like any other property, you can also define the indicative formatter as a property on the validator class.
class StoreUser {
get formatter () {
return 'jsonapi'
}
}
Quite often you want to perform a couple of checks to make sure that the user is authorized to take the desired action. Same can be done by defining authorize
method on validator class.
It is important to return a boolean from the authorize method to tell the validator whether or not to forward the request to the controller.
|
class StoreUser {
async authorize () {
if (!isAdmin) {
this.ctx.response.unauthorized('Not authorized')
return false
}
return true
}
}
module.exports = StoreUser
All route validators can access the current request context via this.ctx
.
AdonisJs supports all the validation rules by Indicative but also adds a few who are specific to AdonisJs only. Below is the list of custom rules.
Makes sure a given value is unique in a given database table.
'use strict'
class StoreUser {
get rules () {
return {
email: 'unique:users,email'
}
}
}
When updating the existing user profile, there is no point of checking their email address when enforcing the unique
rule.
The same can be done by defining an ignoreField (id)
and ignoreValue (userId)
.
class StoreUser {
get rules () {
const userId = this.ctx.params.id
return {
email: `unique:users,email,id,${userId}`
}
}
}
Let’s start by adding a rule which makes sure that the Post exists when we are adding a new Comment to the database. We call it the exists
rule.
The API is same as the Indicative extend API. It is just you add rules via the Validator provider.
|
If your application just uses the rule, then consider adding it to the start/hooks.js
file.
const { hooks } = require('@adonisjs/ignitor')
hooks.after.providersBooted(() => {
const Validator = use('Validator')
const Database = use('Database')
const existsFn = async (data, field, message, args, get) => {
const value = get(data, field)
if (!value) {
/**
* skip validation if value is not defined. `required` rule
* should take care of it.
*/
return
}
const [table, column] = args
const row = await Database.table(table).where(column, value).first()
if (!row) {
throw message
}
}
Validator.extend('exists', existsFn)
})
Now we can use this rule as follows.
get rules () {
return {
post_id: 'exists:posts,id'
}
}
If you are planning to bundle this rule and share it with other as an npm module, then consider wrapping the code inside a provider.
.
└── providers
├── ExistsRuleProvider.js
const { ServiceProvider } = require('@adonisjs/fold')
class ExistsRuleProvider extends ServiceProvider {
existsFn (data, field, message, args, get) {
const value = get(data, field)
if (!value) {
/**
* skip validation if value is not defined. `required` rule
* should take care of it.
*/
return
}
const [table, column] = args
const row = await Database.table(table).where(column, value).first()
if (!row) {
throw message
}
}
boot () {
const Validator = use('Validator')
Validator.extend('exists', this.existsFn.bind(this))
}
}
module.exports = ExistsRuleProvider
The exists
rule is added to the Validator
when someone registers your provider to the providers
array.