Skip to main content

Validation

Understand how you can use the Athenna validation API.

Introduction

Athenna provides several different approaches to validate your application's incoming data by using VineJS under the hood. It is most common to create a validation class extending the BaseValidator class available after installing the @athenna/validator package. However, we will discuss other approaches to validation as well.

Athenna includes a wide variety of convenient validation rules that you may apply to data, even providing the ability to validate if values are unique in a given database table. We'll cover each of these validation rules in detail so that you are familiar with all of Athenna's validation features.

Installation

First of all you need to install @athenna/validator package and configure it. Artisan provides a very simple command to install and configure the validator library in your project. Simply run the following:

node artisan install @athenna/validator

The validator configurer will do the following operations in your project:

  • Add all database commands in your .athennarc.json file.
  • Add all database templates files in your .athennarc.json file.
  • Add all validator providers in your .athennarc.json file.

Validation quickstart

To learn about Athenna's powerful validation features, let's look at a complete example of validating a form and displaying the error messages back to the user. By reading this high-level overview, you'll be able to gain a good general understanding of how to validate incoming request data using Athenna:

Defining routes

First, let's assume we have the following routes defined in our

Path.routes('http.ts')

./src/routes/http.ts

file:

Path.routes('http.ts')
import { Route } from '@athenna/http'

Route.post('/articles', 'ArticleController.store')

Creating the controller

Next, let's take a look at a simple controller that handles incoming requests to this route. We'll leave the store() method empty for now:

Path.controllers('article.controller.ts')
import { Context, Controller } from '@athenna/http'

@Controller()
export class ArticleController {
public async store({ response }: Context) {
// Validator and store the article
const article = /** ... */

return response.status(200).send(article)
}
}

Writing the validation class

To get started, let's create a validator. Validators typically live in the

Path.validators()

./src/validators

and extend the BaseValidator class. You may use the make:validator Artisan command to generate a new validator:

node artisan make:validator article.validator 

Now we just need to create the validation schema and gave a name to our validation class:

Path.validators('article.validator.ts')
import { type Context } from '@athenna/http'
import { v, Validator, BaseValidator } from '@athenna/validator'

@Validator({ name: 'article:create' }) 👈
export class ArticleValidator extends BaseValidator {
public schema = v.object({
title: v.string()
.unique({ table: 'articles' })
.maxLength(255),
body: v.string()
})

public async handle({ request }: Context) {
const data = request.only(['title', 'body'])

await v.validate(data)
}
}

With our schema and name defined in our validation class, it's time to define it in our route:

Path.routes('http.ts')
import { Route } from '@athenna/http'

Route.post('/articles', 'ArticleController.store').validator('article:create')

Now every time that a request to create an article comes, if the validation rule pass, your code will keep executing normally; however, if validation fails, a ValidationException exception will be thrown and the proper error response will automatically be sent back to the user.

Voilà! You have defined you first validator using Athenna 🥳

Validating REST API components

When using validator classes for REST API, keep in mind that they are just middlewares with some helpers to make validation easier. So you are free to define multiple validation classes:

import { Route } from '@athenna/http'

Route.post('/articles', 'ArticleController.store')
.validator('article:create') 👈
.validator('article:update') 👈

There might be some cases where you need to validate fields inside request params or query params. Since validators are just like middlewares, You have access to all that information through the ctx.request object:

Path.validators('article.validator.ts')
import { type Context } from '@athenna/http'
import { v, Validator, BaseValidator } from '@athenna/validator'

@Validator({ name: 'article:create' })
export class ArticleValidator extends BaseValidator {
public schema = v.object({
title: v.string()
.unique({ table: 'articles' })
.maxLength(255),
body: v.string(),
created_by: v.string()
})

public async handle({ request }: Context) {
const data = {
...request.only(['title', 'body']),
created_by: request.query('created_by') 👈
}

await v.validate(data)
}
}

Validation outside of REST API's

For applications where you don't have an easy integration with a validator class or if you simply want to have control over the validation flow you can use the v object directly, v object is the same as vite but smaller to write smaller schemas:

Path.controllers('article.controller.ts')
import { v } from '@athenna/validator'
import { type Context, Controller } from '@athenna/http'

@Controller()
export class ArticleController {
public async store({ request, response }: Context) {
const data = request.only(['title', 'body'])

const schema = v.object({
title: v.string()
.unique({ table: 'articles' })
.maxLength(255),
body: v.string()
})

const article = await v.validate({ schema, data })

return response.status(200).send(article)
}
}

Just like the validation class, if the validation rule pass, your code will keep executing normally; however, if validation fails, a ValidationException exception will be thrown and the proper error response will automatically be sent back to the user.

Available validation rules

All the of the native validation rules can be found in VineJS documentation. Athenna supports all schema types of VineJS, you can find the documentation for each one here:

Custom validation rules

To create custom validations we just need to create a provider where we will use the Validate facade to extend some of the schema types. Let's check in the example bellow a simpler version of the unique custom validation rule of Athenna, let's start by creating a validation provider:

node artisan make:provider customvalidation.provider

With our provider created and registered in .athennarc.json, we just need to make use of the Validate.extend() method inside the boot() method:

import { Database } from '@athenna/database'
import { Validate } from '@athenna/validator'
import { ServiceProvider } from '@athenna/ioc'

type UniqueOptions = {
table: string
}

declare module '@vinejs/vine' {
interface VineString {
unique(options: UniqueOptions): this
}
}

export default class CustomValidationProvider extends ServiceProvider {
public async boot() {
Validate.extend().string('unique', async (value, options, field) => {
/**
* Don't validate non string values, let `string`
* validation rule throw the error.
*/
if (!Is.String(value)) {
return
}

const existsRow = await Database.table(options.table)
.select(options.column)
.where(options.column, value)
.exists()

if (existsRow) {
field.report('The {{ field }} field is not unique', 'unique', field)
}
})
}
}
tip

For more information around how to create custom rules for your schemas please rely on VineJS custom rules documentation.

Custom validation messages

For custom validation messages you can use the Validate.extend().messages() method. So following the same approach of creating custom validation rules, we need to create a service provider to call this method:

import { Validate } from '@athenna/validator'
import { ServiceProvider } from '@athenna/ioc'

export default class CustomValidationProvider extends ServiceProvider {
public async boot() {
Validate.extend().messages({
// Applicable for all fields
'required': 'The {{ field }} field is required',
'string': 'The value of {{ field }} field must be a string',
'email': 'The value is not a valid email address',

// Error message only for the username field
'username.required': 'Please choose a username for your account'

// For arrays
'contacts.0.email.required': 'The primary email of the contact is required',
'contacts.*.email.required': 'Contact email is required'
})
}
}
tip

For more information around syntaxes you can use whe creating custom error messages please rely on VineJS custom error messages documentation.