Skip to main content
AdonisJS uses VineJS for request validation, providing a type-safe and expressive way to validate incoming HTTP request data.

Basic validation

Validate request data using the request.validateUsing() method:
import router from '@adonisjs/core/services/router'
import vine from '@vinejs/vine'

// Define a validator
const createUserValidator = vine.compile(
  vine.object({
    email: vine.string().email(),
    name: vine.string().minLength(3),
    password: vine.string().minLength(8)
  })
)

// Use in route handler
router.post('/users', async ({ request }) => {
  const data = await request.validateUsing(createUserValidator)
  
  // data is now typed as { email: string, name: string, password: string }
  const user = await User.create(data)
  return { user }
})

RequestValidator class

The RequestValidator class from modules/http/request_validator.ts:34 integrates VineJS with AdonisJS requests:
import { RequestValidator } from '@adonisjs/core/http'

/**
 * The RequestValidator provides:
 * - Automatic validation of request body, files, URL parameters, headers, and cookies
 * - Custom error reporters per request
 * - Custom message providers per request
 */

Validation data

By default, request.validateUsing() validates:
  • Request body
  • Uploaded files
  • URL parameters (available as params)
  • Request headers (available as headers)
  • Cookies (available as cookies)

Validating request body

const updateUserValidator = vine.compile(
  vine.object({
    email: vine.string().email().optional(),
    name: vine.string().minLength(3).optional(),
    bio: vine.string().maxLength(500).optional()
  })
)

router.patch('/users/:id', async ({ request }) => {
  const data = await request.validateUsing(updateUserValidator)
  // Only includes fields from request body
})

Validating URL parameters

const showUserValidator = vine.compile(
  vine.object({
    params: vine.object({
      id: vine.number()
    })
  })
)

router.get('/users/:id', async ({ request }) => {
  const { params } = await request.validateUsing(showUserValidator)
  // params.id is now a number
})

Validating headers

const apiRequestValidator = vine.compile(
  vine.object({
    headers: vine.object({
      'api-key': vine.string(),
      'accept': vine.string()
    })
  })
)

router.post('/api/data', async ({ request }) => {
  const { headers } = await request.validateUsing(apiRequestValidator)
  // headers are validated
})

Validating cookies

const cookieValidator = vine.compile(
  vine.object({
    cookies: vine.object({
      session_id: vine.string()
    })
  })
)

router.get('/dashboard', async ({ request }) => {
  const { cookies } = await request.validateUsing(cookieValidator)
  // cookies.session_id is validated
})

Custom validation data

Provide custom data to validate:
router.post('/users', async ({ request }) => {
  const customData = {
    email: 'user@example.com',
    name: 'John Doe'
  }
  
  const data = await request.validateUsing(createUserValidator, {
    data: customData
  })
})

Error handling

Validation errors throw a ValidationError:
import { errors as vineErrors } from '@vinejs/vine'

router.post('/users', async ({ request, response }) => {
  try {
    const data = await request.validateUsing(createUserValidator)
    const user = await User.create(data)
    return { user }
  } catch (error) {
    if (error instanceof vineErrors.E_VALIDATION_ERROR) {
      return response.badRequest({
        errors: error.messages
      })
    }
    throw error
  }
})

Try validation

Use tryValidateUsing() for validation without throwing errors:
router.post('/users', async ({ request, response }) => {
  const [error, data] = await request.tryValidateUsing(createUserValidator)
  
  if (error) {
    return response.badRequest({
      errors: error.messages
    })
  }
  
  const user = await User.create(data)
  return { user }
})
From modules/http/request_validator.ts:134:
/**
 * Returns a tuple of [ValidationError, null] on failure
 * or [null, ValidatedData] on success
 */
async tryValidateUsing<Schema, MetaData>(
  validator: VineValidator<Schema, MetaData>,
  options?: RequestValidationOptions<MetaData>
): Promise<[ValidationError, null] | [null, Infer<Schema>]>

Custom error reporters

Customize how validation errors are reported:
import { SimpleErrorReporter } from '@vinejs/vine'
import { RequestValidator } from '@adonisjs/core/http'

// Set global error reporter
RequestValidator.errorReporter = ({ request }) => {
  // Return custom error reporter based on request
  if (request.accepts(['json'])) {
    return new SimpleErrorReporter()
  }
  return new SimpleErrorReporter()
}

Per-request error reporter

import { SimpleErrorReporter } from '@vinejs/vine'

router.post('/users', async ({ request }) => {
  const data = await request.validateUsing(createUserValidator, {
    errorReporter: () => new SimpleErrorReporter()
  })
})

Custom messages

Provide custom validation messages:
import { SimpleMessagesProvider } from '@vinejs/vine'
import { RequestValidator } from '@adonisjs/core/http'

// Set global messages provider
RequestValidator.messagesProvider = () => {
  return new SimpleMessagesProvider({
    required: 'The {{ field }} field is required',
    'email.email': 'Please provide a valid email address',
    'password.minLength': 'Password must be at least {{ min }} characters'
  })
}

Per-request messages

import { SimpleMessagesProvider } from '@vinejs/vine'

const customMessages = new SimpleMessagesProvider({
  required: 'This field cannot be empty',
  'email.email': 'Invalid email format'
})

router.post('/users', async ({ request }) => {
  const data = await request.validateUsing(createUserValidator, {
    messagesProvider: customMessages
  })
})

Validation metadata

Pass metadata to validators for dynamic validation:
const createUserValidator = vine.withMetaData<{ disallowUserNames: string[] }>().compile(
  vine.object({
    username: vine.string().notIn((field) => {
      return field.meta.disallowUserNames
    })
  })
)

router.post('/users', async ({ request }) => {
  const data = await request.validateUsing(createUserValidator, {
    meta: {
      disallowUserNames: ['admin', 'root', 'system']
    }
  })
})

Creating validators

Inline validators

router.post('/posts', async ({ request }) => {
  const data = await request.validateUsing(
    vine.compile(
      vine.object({
        title: vine.string().minLength(5),
        content: vine.string(),
        published: vine.boolean()
      })
    )
  )
})

Reusable validators

Create validators in separate files:
// app/validators/user.ts
import vine from '@vinejs/vine'

export const createUserValidator = vine.compile(
  vine.object({
    email: vine.string().email(),
    name: vine.string().minLength(3),
    password: vine.string().minLength(8)
  })
)

export const updateUserValidator = vine.compile(
  vine.object({
    email: vine.string().email().optional(),
    name: vine.string().minLength(3).optional(),
    bio: vine.string().maxLength(500).optional()
  })
)
Use in controllers:
import { createUserValidator, updateUserValidator } from '#validators/user'

export default class UsersController {
  async store({ request }: HttpContext) {
    const data = await request.validateUsing(createUserValidator)
    const user = await User.create(data)
    return { user }
  }
  
  async update({ request, params }: HttpContext) {
    const data = await request.validateUsing(updateUserValidator)
    const user = await User.findOrFail(params.id)
    await user.merge(data).save()
    return { user }
  }
}

Common validation rules

String validation

vine.object({
  email: vine.string().email(),
  url: vine.string().url(),
  username: vine.string().minLength(3).maxLength(20),
  bio: vine.string().maxLength(500).optional(),
  role: vine.enum(['admin', 'user', 'moderator'])
})

Number validation

vine.object({
  age: vine.number().min(18).max(100),
  price: vine.number().positive(),
  quantity: vine.number().min(1),
  rating: vine.number().min(1).max(5).optional()
})

Boolean validation

vine.object({
  isPublished: vine.boolean(),
  acceptTerms: vine.boolean().isTrue() // Must be true
})

Array validation

vine.object({
  tags: vine.array(vine.string()).minLength(1).maxLength(5),
  ids: vine.array(vine.number()),
  roles: vine.array(vine.enum(['admin', 'user']))
})

Object validation

vine.object({
  profile: vine.object({
    firstName: vine.string(),
    lastName: vine.string(),
    age: vine.number().optional()
  })
})

File validation

vine.object({
  avatar: vine.file({
    size: '2mb',
    extnames: ['jpg', 'png', 'jpeg']
  }),
  documents: vine.array(
    vine.file({
      size: '5mb',
      extnames: ['pdf', 'doc', 'docx']
    })
  ).optional()
})

Best practices

Never trust user input. Always validate data before processing:
const data = await request.validateUsing(validator)
// Now data is validated and type-safe
Store validators in a dedicated directory for reuse:
app/
└── validators/
    ├── user.ts
    ├── post.ts
    └── comment.ts
VineJS validators provide full TypeScript support:
const data = await request.validateUsing(createUserValidator)
// data is typed as { email: string, name: string, password: string }
Provide clear error messages to users:
const [error, data] = await request.tryValidateUsing(validator)

if (error) {
  return response.badRequest({
    message: 'Validation failed',
    errors: error.messages
  })
}

Further reading

VineJS Documentation

Learn more about VineJS validation rules and features