Validator

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.

Setup

Follow the below instructions to setup the validation provider.

Install

adonis install @adonisjs/validator

Registering provider

Next, you need to register the provider inside start/app.js file.

const providers = [
  '@adonisjs/validator/providers/ValidatorProvider'
]

That is all 🎉

Validating user input

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.

  1. First, we have defined the rules schema.

  2. Next, we make use of the validate method to validate the request data with our defined rules.

  3. If validation fails, we flash all the errors and redirect the user back to the form.

Showing flash errors

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>

Validator methods

Below is the list of available methods.

validate(data, rules, [message])

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()
}

validateAll

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)

sanitize(data, rules)

This method returns a new object with sanitized data.

const { sanitize } = use('Validator')
const data = sanitize(request.all(), rules)

is

Returns a reference to indicative raw validator.

const { is } = use('Validator')

if (is.email('[email protected]')) {
}

sanitizor

Returns a reference to indicative raw sanitizor.

const { sanitizor } = use('Validator')
const slug = sanitizor.slug('My first blog post')

formatters

Returns a reference to formatters

const { formatters } = use('Validator')
formatters.default('jsonapi')

// adding new formatter
formatters.register('custom', CustomImplementation)

Route validator

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

Sanitizing user input

Also, you can sanitize the user input by defining the sanitizationRules. The sanitization happens before the validation.

class StoreUser {
  get sanitizationRules () {
    email: 'normalize_email',
    age: 'to_int'
  }
}

module.exports = StoreUser

Handling validation failure

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

Custom data object

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 = request.all()
    const sessionId = request.header('X-Session-Id')

    return Object.assign({}, requestBody, { sessionId })
  }
}

module.exports = StoreUser

Formatter

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'
  }
}

Authorization

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

Request context

All route validators can access the current request context via this.ctx.

Custom Rules

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.

unique(tableName, [fieldName], [ignoreField], [ignoreValue])

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}`
    }
  }
}

Extending Validator

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.

Application specific

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'
  }
}

Via Provider

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.

Directory structure
.
└── providers
    ├── ExistsRuleProvider.js
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.