Vue 3 and Validation with the Class-Validator
Shawn Wildermuth
Posted on August 19, 2020
I've been working with Vue 3 Beta and RC (currently in RC5) and early on I needed some validation but the Vue stalwards of vuelidate and vee-validate weren't working with the Composition API early on. What was I do to?
After some searching I ran into class-validator library. It got me thinking about how to separate the validation from the UI like I usually do in the server.
I thought I'd run you though a little example. If you want to take a look at the project, I have an example on GitHub with tags for before and after:
Let's get started, first let's look at the class-validator library. For example, I have a model that my project uses that looks like this:
export default class Customer {
id = 0;
fullName: string | undefined;
firstName: string | undefined;
lastName: string | undefined;
phoneNumber: string | undefined;
companyName: string | undefined;
addressLine1: string | undefined;
addressLine2: string | undefined;
addressLine3: string | undefined;
cityTown: string | undefined;
stateProvince: string | undefined;
postalCode: string | undefined;
}
To use this, I have to make sure that the TypeScript configuration (tsconfig.json) supports decorators:
{
"compilerOptions": {
...
"experimentalDecorators": true,
...
I brought in the library by:
> npm install class-validator --save
After importing the decorators I applied some validations:
export default class Customer {
id = 0;
fullName: string | undefined;
@MinLength(3, {
message: "Must be > 3 characters"
})
firstName: string | undefined;
@MinLength(5, {
message: "Must be > 5 characters"
})
lastName: string | undefined;
@IsOptional()
@IsPhoneNumber("US", { message: "Must be a valid phone number" })
phoneNumber: string | undefined;
@IsOptional()
@MinLength(5, {
message: "Must be > 5 characters"
})
companyName: string | undefined;
@IsDefined({
message: "Address is required"
})
addressLine1: string | undefined;
addressLine2: string | undefined;
addressLine3: string | undefined;
@IsDefined({
message: "City is required"
})
cityTown: string | undefined;
@IsDefined({
message: "State is required"
})
@Length(2, 2, {
message: "Must be a US State"
})
stateProvince: string | undefined;
@IsDefined({
message: "Zipcode is required"
})
@Matches(/^[0-9]{5}(?:-[0-9]{4})?$/, {
message: "Must be valid Zipcode"
})
postalCode: string | undefined;
}
The decorators feel a lot like .NET validation. What I really like is that it's not a plugin for Vue so similar code could be used in different platforms or even in Node.
The class-validation library has a fairly simple function called validate that takes the object to validate and return a set of errors if validation fails.
let result = await validate(someObj);
for(const error of result) {
// ...
}
To use this, I decided to make a base class for the model to check the validation on any of the models:
import { validate, ValidationError } from "class-validator";
export default abstract class BaseModel {
public errors: Object;
constructor() {
this.errors = {};
}
public get isValid(): boolean {
return Object.keys(this.errors).length === 0;
}
public async validateModel() {
let result = await validate(this);
this.errors = this.setError(result)
}
private setError(result: ValidationError[]): Object {
let propBag = {};
for (const error of result) {
for (const key in error.constraints) {
if (Object.prototype.hasOwnProperty.call(error.constraints, key)) {
const msg = error.constraints[key];
(propBag as any)[error.property] = msg;
}
}
}
return propBag;
}
}
This way in the view I can simply bind to the errors collection:
<div class="form-group">
<label for="firstName">First Name</label>
<input class="form-control" name="firstName" v-model="customer.firstName" />
<span
v-if="customer.errors.firstName"
class="text-danger small p-0 m-0"
>{{ customer.errors.firstName }}</span>
</div>
This snippet shows that I'm binding to the errors collection where I'd have a property per field that has an error. I flatten the errors collection a bit in the base class (see the setError function).
In this way the rules are no longer in the UI but should match the server-validation.
Any ideas on how to improve this?
This work by [Shawn Wildermuth](http://wildermuth.com) is licensed under a [Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License](http://creativecommons.org/licenses/by-nc-nd/3.0/).
Based on a work at [wildermuth.com](http://wildermuth.com).
If you liked this article, see Shawn's courses on Pluralsight.
Posted on August 19, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.