When I first needed some basic form of validation in my Vue.js application, I discovered and used Vuelidate, a popular validation library written specifically for Vue.js. Vuelidate is simple to use and works pretty well inside Vue.js components. It validates all different data sources in a component, based on the validation criteria provided as a separate entry in the component’s definition. Since this happens automatically, we can just query the validation results directly whenever we need it from automatically populated result objects.

This approach is not bad for some use cases, but the problem is that we often need to validate data elsewhere in the application, not only in the components. I asked in the Is it possible to use Vuelidate outside of Vue.js component? thread on Vue.js forum and it seems like we need at least an instance of Vue.js to perform the validation and cannot use Vuelidate without it.

This kind of dependency doesn’t feel right when we want to validate data in some middle layer of the application, in a separate module outside of the Vuex store or Vue.js component. That’s why I started to look around again and finally decided to use validate.js. Validate.js is a framework-agnostic validation library for JavaScript and can be used anywhere in our Vue.js applications and also in applications written without Vue.js.

In this post I want to show the different approaches between using Vuelidate and validate.js. I will demonstrate how to validate a form for adding addresses. For brevity I am showing just minimal HTML and JavaScript code.

This is our component template, with two text inputs and a button that will be enabled only when both of the inputs are filled:

<template> <div> <h3>Add address</h3> <div> <input v-model="address.name" type="text" id="name" name="name" /> <input v-model="address.addressLine1" type="text" id="addressLine1" name="addressLine1" /> <button :disabled="!validAddress" @click="addAddress"> Add address </button> </div> </div> </template> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template> <div> <h3> Add address </h3> <div> <input v-model = "address.name" type = "text" id = "name" name = "name" /> <input v-model = "address.addressLine1" type = "text" id = "addressLine1" name = "addressLine1" /> <button : disabled = "!validAddress" @ click = "addAddress" > Add address </button> </div> </div> </template>

This is our corresponding component code utilizing Vuelidate:

import { required } from "vuelidate/lib/validators" export default { data() { return { address: { name: undefined, addressLine1: undefined } } }, computed: { validAddress() { return this.$v.address.$invalid == false } }, methods: { addAddress() { // TODO: save address } }, validations: { address: { name: { required }, addressLine1: { required } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import { required } from "vuelidate/lib/validators" export default { data ( ) { return { address : { name : undefined , addressLine1 : undefined } } } , computed : { validAddress ( ) { return this . $ v . address . $ invalid == false } } , methods : { addAddress ( ) { // TODO: save address } } , validations : { address : { name : { required } , addressLine1 : { required } } } }

Here, validation rules are defined in the validations section, mirroring the structure in data section. Note that required validator has to be imported from Vuelidate which means we cannot represent the validation object with plain JSON. On the other hand, the presence of this section alone is enough for validation to work automatically for our address object, and it would work automatically for computed properties too.

In the validAddress computed property we can see that all it takes to know whether the address is valid is to query the Vuelidate $v.address object. It offers various properties to check the results. We can see that the validation of both of the properties is combined into a parent validation without any work on our side.

Now, let’s see how the component would look like if written with validate.js. I decided to separate the code into the component, the validation functions and the validation rules. First let’s have look at the component:

import { validateAddressProp } from "../../services/addressService" export default { data() { return { address: { name: undefined, addressLine1: undefined } } }, computed: { validAddressName() { return validateAddressProp("name", this.address.name) }, validAddressLine1() { return validateAddressProp("addressLine1", this.address.addressLine1) }, validAddress() { return this.validAddressName && this.validAddressLine1 } }, methods: { addAddress() { // TODO: save address } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import { validateAddressProp } from "../../services/addressService" export default { data ( ) { return { address : { name : undefined , addressLine1 : undefined } } } , computed : { validAddressName ( ) { return validateAddressProp ( "name" , this . address . name ) } , validAddressLine1 ( ) { return validateAddressProp ( "addressLine1" , this . address . addressLine1 ) } , validAddress ( ) { return this . validAddressName && this . validAddressLine1 } } , methods : { addAddress ( ) { // TODO: save address } } }

The component code is the same, with the exception of the validAddress computed property, which now calls our validation function validateAddressProp from separate module. This function validates a given property for us and we will see its definition in the next code listing. As we can see, now the validation itself is delegated outside of the component and the only thing we need to write are the individual computed properties, like validAddressName and validAddressLine1 .

To see how validate.js is utilized, we need to have a look at our addressService.js module. This is our middle layer module with two validation functions. I included two functions so that we can have a better idea how to validate a complete object and how to validate just a single property from it:

import validate from "../utils/validatejs" import Address from "../model/address" export function validateAddressProp(propName, value) { return validate.validate({ [propName]: value }, { [propName]: Address.getConstrains()[propName] }) === undefined } export function validateAddress(address) { return validate.validate(address, Address.getConstrains()) === undefined } 1 2 3 4 5 6 7 8 9 10 import validate from "../utils/validatejs" import Address from "../model/address" export function validateAddressProp ( propName , value ) { return validate . validate ( { [ propName ] : value } , { [ propName ] : Address . getConstrains ( ) [ propName ] } ) === undefined } export function validateAddress ( address ) { return validate . validate ( address , Address . getConstrains ( ) ) === undefined }

This module could contain our validation rules for address object directly, but I wanted to show you that we can store this validation rules nicely within the domain class itself.

So what we can achieve in the end is to have all the validation constrains in our Address domain class, but without any dependency on validate.js. I think that this is pretty cool:

export default class Address { constructor(name) { this.name = name this.addressLine1 = null } static getConstrains() { return { name: { presence: { allowEmpty: false } }, addressLine1: { presence: { allowEmpty: false } } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 export default class Address { constructor ( name ) { this . name = name this . addressLine1 = null } static getConstrains ( ) { return { name : { presence : { allowEmpty : false } } , addressLine1 : { presence : { allowEmpty : false } } } } }

To recap:

Because Validate.js is framework agnostic, we can use it in all sorts of JavaScript applications and we can easily reuse parts of the validation logic between our applications.

Integrating validate.js into Vue.js application is not that difficult and we can use it anywhere, without a dependency on Vue.js instance.

Validate.js doesn’t require any dependency in the constrains definition which is a nice bonus.

Note that I didn’t really explain the APIs of the mentioned libraries, but both Vuelidate and validate.js have great documentation, so I hope it is not necessary. My main goal was to demonstrate that we can utilize validate.js to make our Vue.js applications more flexible and cleaner. All it takes is just a little bit more work, but I think it is worth it.