Validation with VineJS
Form data cannot be trusted: the title may be left empty, and a number field may receive text. Validation is checking incoming data against rules before it reaches the database. AdonisJS uses the VineJS library for this: rules are described as a schema, and the result of the check gets exact TypeScript types.
Creating a validator
node ace make:validator postThe file app/validators/post.ts appears. Describe the rules for a new post:
// app/validators/post.ts
import vine from '@vinejs/vine'
export const createPostValidator = vine.compile(
vine.object({
title: vine.string().trim().minLength(3).maxLength(255),
body: vine.string().trim().minLength(10),
})
)vine.object describes the expected fields, vine.string() requires a string, and trim, minLength and maxLength refine the rules. vine.compile turns the schema into a fast check function once. All fields are required by default.
Checking in a controller
// app/controllers/posts_controller.ts
import type { HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import { createPostValidator } from '#validators/post'
export default class PostsController {
async store({ request, response }: HttpContext) {
const payload = await request.validateUsing(createPostValidator)
const post = await Post.create(payload)
return response.redirect().toRoute('posts.show', { id: post.id })
}
}validateUsing takes the request data and checks it. If everything is valid, payload contains only the described fields, typed as { title: string; body: string }. Extra fields, such as isAdmin, do not get into it.
What happens on an error
If the check fails, the code after validateUsing does not run. Then AdonisJS acts according to the request type:
| Request | Response |
|---|---|
| A regular HTML form | A redirect back, errors and entered values are saved in the session |
A JSON request (header Accept: application/json) | Status 422 and JSON with the list of errors |
How to show errors next to form fields is covered in the next lesson.
Common rules
| Rule | What it checks |
|---|---|
vine.string().email() | An email address |
vine.number().min(1) | A number not less than 1 |
vine.boolean() | Yes or no, including a form checkbox |
vine.enum(['draft', 'published']) | One of the values |
vine.string().optional() | The field may be omitted |
vine.string().confirmed() | Matches the ..._confirmation field |
Custom messages
You can replace the standard VineJS messages with your own texts through a messages provider. Create a preload file with node ace make:preload validator and write in it:
// start/validator.ts
import vine, { SimpleMessagesProvider } from '@vinejs/vine'
vine.messagesProvider = new SimpleMessagesProvider({
'required': 'The {{ field }} field is required',
'minLength': 'The {{ field }} field must be at least {{ min }} characters',
'title.minLength': 'The title is too short',
})The title.minLength key sets a message for one field and one rule, and the general minLength key covers all the others.
Common mistake
A validator is created inside a controller method: const validator = vine.compile(...) right in store. Then the schema is compiled again on every request, which noticeably slows the application down. Declare validators in app/validators/ at the top level of the module, and import the ready ones into the controller.