Vue Form Validations with Vest


Forms on the web have always been with us, yet it still feels like you always have to fight them to make validation work as you planned - and even with the significant improvements modern day libraries and frameworks give us, maintenance is mostly a hassle.

Vest is a new breed of form validations framework. It draws its syntax and style from Unit Testing frameworks like mocha or Jest (hence the name), which brings the elegance and declarative nature of these frameworks to the world of form validation, greatly reducing the overhead it takes to write, maintain and reuse validations in your JS apps.


Hey, I'm Avatar, I'm a front-end engineer at Facebook and I'm the author of VEST. VEST is a form validation framework inspired by unit testing libraries like Mocha or Jest. So if you've done even a little unit testing in your career, I think you'll feel very much at home working with VEST. Today I want to show you how we can use VEST to improve the way we write form validations in our Vue apps. But before I start speaking about VEST itself, I want to mention a little the motivation behind VEST and what led me to write VEST to begin with. In my experience writing forms and building forms before using VEST, I had a big problem of lacking structure. So I was trying to add validations to the form and I wasn't sure where I should put the validation logic. Should I put them inside the change handlers? Should I put them somewhere in my feature in a shared library? How do I write it? How do I avoid it being too specific to my feature? And there is no any specific structure that the validations should follow. So I ended up making it work by writing somewhere in between my handlers and my feature. But then when I wanted to make changes and maintain the feature, like adding more fields in the future or making fields dependent on one another or even removing a field, it was very, very hard because everything was tied down to the feature. And because everything is very specific to the feature, it was very hard as well to make use of again. So to take it and use it in a different form or a different feature, like the password field in both reset password and sign in. So all these led me to think of a solution. And a couple of years back when I was working with a previous employer, we just started writing unit tests for our apps. And I saw that patterns that unit tests have that we have that testing suite with describes and it's and expects, and it looked very similar to the way I was thinking about form validation in my mind, because unit tests are declarative by nature. So you are able to describe exactly what you want to happen. And along with that, they are very good at expressing what's there compared to how it should be. So you put a function function in a test. And the same goes for form validation. So I want my values, my data to run through some tests. And it all seems very relevant to the world of form validation. Of course, it's not exactly the same and a term terminology is different and we don't run unit tests in production. But with some design adjustments, I was able to come up with something that's still very similar to the way we write unit tests and still be very relevant for form validation. And the structure I came up with is this. We first create a suite that's separate from our feature code and add a callback to it. Inside the callback, we add our tests, similar to unit test tests, with an extra field or an extra parameter, which is the name of the field that we're validating. So in this case, we have test, and we're testing that username. And then we have the error that the user will get in case of a validation failure. So username must be at least three characters. And inside the callback of that test, we have our assertions. So similar to assert or expect, we enforce the data.username is longer than two or whatever validation we have there. And I want to show you how it works with a real live Vue app. And just note, I'm using here the options API, but it could work with the composition API just as well. No changes whatsoever to the best code. So this is our app. It's a very basic app without any validation at all yet. And I added some input components that are there just for styling. I added a few class names. So I added a class name for error that turns it red. Let me just refresh. Okay, it turns it red. I added a class name for warning that turns it orange, and one for success that turns it green. And along with that, I also added a few props. So one for errors, and it takes an array of strings, and when displayed, it shows the error on the field. And same for warnings. I also added a loading spinner because we're going to do some async validations later down the line. So loading true. And we're going to see a spinner. So that's all we have already here. We also have the validate function, which is empty. It takes the name and the value from the field that we're validating. And let's create our suite. So source. And let's import create from Vest. Now I'll do const suite equals create. And I'll export default.suite. So export default suite. Now import this suite into my form. So import suite from suite. And I'll use it inside my validate function. So suite, and I'll call it. Now I'll store the validation result in our data. So res equals suite.get. And I'll also update it whenever there's a change in the validate function. So this.res equals the result from suite. And let's take the value and the name and also pass down the this.input. So let's pass everything and the name of the field that we're validating. Back to the suite, let's write our first test. So first, let's take the data, which is empty at the moment. And let's take test and enforce. Now writing the first test is very simple. Let's test that username and say that if it fails, the user will say username is required. And then enforce that data.username is not empty. And if I go back to the form, and I already have this here, I'll just go to the input and do errors equals and then res.get errors or the field username. And if I start typing and then remove everything, I'll see that username is required, which is what we wrote. Let's duplicate this for password as well. And let's go to write the rest of our test. So another one for username, and we can have multiple tests for the same field. And they do not have to be in the same function, which makes it very easy to understand what's going on. So username must be at least three characters long, longer than two. Let's do the same for password. And let's say that password must be at least five characters, so longer than or equals five. And now if I refresh and start typing, we'll see that we get the validation message for username and also for password. Now if you noticed it, the password field starts lighting up whenever I type inside username, which is not the expected result. We actually want it to only fire up for itself. And this is really easy. Let's just import only. And only allows us to specify the fields that we want to be validated at any given time. So let's run only. And remember we're passing the name of the field that we're validating to the suite. We can take it here, current field. And let's pass it to only. And now whenever I type inside any of the fields, only it will light up. Now let's add some color. Vest is not a UI framework, but it does give you some UI utilities so you can style your app. So let's do import class names from vest class names. And what the class names utility does is it gives you an ability to specify which class names should appear in which validation state. So I'll do a computed value and I'll use and I'll return class names and pass it the validation result and also the classes that I want to appear at any stage. So if it's valid, I want it to be success. And if it's invalid, I want it to be error. And if it's warning, I want it to be warning. So I can take now the class names and put it in the class. So class equals CN, that's our property, with the field username. And if I start typing, we'll see that it's red and then it's green. So let's do the same for password. And this is happening the same. Now what if we want some warning fields? So for example, password strength shouldn't block the field from submitting the form from submitting, but it should notify the user there's a problem. Let's do it. We have the feature of warning validations. We just have to call the warn function from vest. So let's do let's test the password. And let's say that it's weak, that it has no numbers. Password is weak. Maybe add a number. And let's do matches, which takes a regular expression. And let's do it has zero to nine in it. And now all I have to do is just add warn to it, which says this is a warning field. And as I type, you'll see that after five characters, it becomes orange, but we're not seeing the validation message. And this is because we're only taking the validation errors. So we have to duplicate this and say, get warnings. And if I start typing now, password is weak, maybe add a number. Cool. Sometimes we want to also have async validations. For example, if the user name is already taken, we want to check that instead of letting the user submit and only then tell them. And all we have to do in vest is just return a promise or an async test inside our suite. So I'll just do it. Let's have a mock function that implements does user exist logic. So async function does user exist. It takes a username. And if the username equals some user, we should throw an error, throw new error. And also, let's make it wait for a second, I'll get the wait npm package. So a wait, wait for one second. And all I have to do now is write a test that returns does user exist. So test username, username already taken. And I just need to return does user exist and call it with data dot username. Now if I start typing here, some user, you'll notice that nothing happens. And this is because best needs to report back to the form that the validation completed. And here it's waiting for a second. So all we have to do and maybe first let's add a spinner. So we know what's going on. Let's add a new data property. So username loading equals false. This is the default. And now let's do if name so if the field that we're validating is username, let's do this dot username is loading equals to true. And now just to make it work, let's do loading equals to username is loading. So just to see okay, we have the spinner and now we need to cancel it when the validation completes. So let's do this dot done. Oh, this dot res dot done. Done is a function that gives us a callback when the validation is complete. And all we have to do is this dot res equals sweet dot get gives us the current validation result. And also let's cancel username is loading. So this dot username loading equals false. So now if I type some user, we're supposed to get username is already taken. And that's it. That's all you have to do for async validation. But of course, it can be improved. Because if you notice, you'll see that I start typing and even though the validation is failing for the shorter validation, for example, the must be at least three characters, we're still showing the spinner, which means we're still going out of the server, which is not a great idea because it could be a very costly async validation. And to prevent that we can just tell Vest to skip a validation upon some criteria. In this case, whenever the validation is failing to begin with. So in our case, let's get skip when from Vest. And it allows us to tell whenever we want to skip something. And we're taking a skip when and our criteria is sweet dot get, which gives us the validation result has errors for username. So whenever username is failing, don't run what's up, whatever's inside that callback. And if I do this, okay, let's try typing something. E, A, no spinner, L, there's a spinner. So we're not validating the async validation before it's ready. Now let's do some user again. And it's correct. But now I'm adding another character. And we went to the server and now it's valid. And I'll remove it. And we went to the server again, even though we do know that the validation is already failing because we've already seen it failing for the same user. And again, it could be a costly validation. And we can handle it with memoization within Vest. And all we have to do is take our test and do test dot memo. And then just add our dependencies. So in our case, we want to memoize it by the username. So data dot username. So as long as username doesn't change, or as long as username is repeated, the validation will give us immediately the same response without going to the server. And this is the example. So some user, we're going to the server. I'm adding a character. And if I remove it, you'll see that we got an immediate response. And now we will see that the form is valid. Everything is okay. I just want to add one more thing. I want to disable submission before the form is fully valid. And this is easy to do. All we have to do is go to our template again, and then add disabled to our submit button. And say we want to disable when res is not valid. So not res is valid. And let me see. Oh, disabled. Okay. And now it's disabled. And if I write something, some user one, perfect. And password example one. And just notice it. Warning does not prevent the form from submission. A warning field does not qualify for invalid, which is just what we're after. And just like that, within I think six lines of logic within Vue, we were able to validate this form completely with many, many complex validations, which is pretty awesome, I think. And we've seen some of the features that Vest has, but not all of them. There are a few more. And there are validation of changed fields upon interaction, which we've done. Multiple validations per field. Warning validations. We can have interdependent field validation, so we can have tests that's dependent on one another. We have async validations, and we can also memoize them. We can group tests and nest tests within groups, for example, when we have a multi-step form. And in general, we have structured validations. These are some of Vest's features, but not all of them. And even with all those features and all this complexity, Vest is still less than six kilobytes when minizipped. And I think it makes Vest one of the smaller, yet most powerful validation frameworks out there. And I am really excited working about Vest. I enjoy writing form validations with Vest. I thank you for joining me today. If you're excited about Vest today, feel free to ping me on Twitter, or even go to the GitHub page, or even submit a PR, improving the docs for Vue, or even improving and adding some code. I really appreciate it. Thank you very much for joining me today. Have a great day. But first, let's look at the results of the poll question he asked, which was about our coffee consumption. It would appear that all of us, or the vast majority of us, have a caffeine problem, myself included. I think Amitar and I had the same answer, if you would like to share what our problem is. Yeah. So it was not about your caffeine problem. It was about my caffeine problem. Just to clarify. And yes, my option was the third one, while sure, drink coffee. Definitely. So yeah, I was just trying to make myself comfortable about my consumption, to make it clear. Well, you're in great company, because I'm great company, and that's what matters. Okay. Let's move on to some questions. First question. I am sorry, I don't want to butcher your name, but your initials are JH. How does the API cope with async validation logic? For example, validation relies on some other async API, like HTTP requests. So this is something that I did show in my presentation. The question was probably asked before that part. And all you have to do is just pass, instead of just passing a validation function, you just pass an async function, or return a promise. And if that function rejects or throws an error, then a validation fails. And it works exactly the same as it does with any other async validation. Nice. Okay, next question from Safady. I'm sorry if I'm butchering names, I really am. Does VEST support JSON schema validation, or does it have integration with JSON schema validators, such as AJV? So VEST, as a general concept, is not a schema validation library, even though it does integrate with anything. And just as a quick note, Enforce, the assertion function inside VEST has very extensive schema validation functions. It is not the most recommended ways, because the way I think forms should be validated are via different tests for each field, but these are supported, and if you are using schema validations in your forms, then yeah, of course, you can actually use the same schema validations inside your tests. So as long as it either throws an error or returns a Boolean, you can put it inside your validation test. Nice. The next question from Organized Chaos. I like the people who have normal words. The question is, very interesting using test patterns for form validation. It seems pretty doable. Have you found any gotchas in writing the tests or other pitfalls? Yes, plenty of them. So writing VEST was not simple, because I was trying to take some patterns that come from a completely different world into the world of form validation. And the main issues I was facing is that unit tests are basically stateless. So every time you run them, all the state gets generated over and over again. And what I had to do to make VEST actually worthwhile and not just waste your time managing your own state is have an internal state that retains the validation results for each of the fields that get validated. And then you only have to do the nice part of writing the custom validations. Okay. Next question is from Anonymousio. I use Nuxt, Vuetify, and Jest. If I use VEST, how about integrating Nuxt, Vuetify, and Vuex and all the libraries? And wow, that was a lot of name dropping. Nuxt, Vuetify, and Vuex, and all the libraries. And all of them. Actually, because VEST is a framework that takes a value, takes some inputs, and returns a validation result, you can integrate it with anything. And it's intentionally not tied into one specific framework or any specific library. So it's very versatile, and you can use it in many different concepts and aspects and in many different contexts. So you can use it with Nuxt, without Nuxt, or you can use it with Vuex, or if you're coming over from React, you can just migrate your validations and not have to change things just to integrate with a specific framework. Okay. And we actually have a question from our wonderful question moderator today. So shout out to all the hard work from, I am going to butcher this, Ayatanasov. I'm so sorry. But the question is, does VEST support file type validation, or do you plan on applying it? Oh, yeah. That's a good one. And this is something I've seen in many other frameworks as well, or libraries for validation. And they typically, some of them do, but they typically do not support some kind of validations. VEST is one of them, of the frameworks that do not support these validations. And it's true for file type or any file measurement validation. It's true for email validation, and it's true for some other ones. And the reason is that these are very business-centric and business-specific. And each time you try to validate something that's specific to any file size, any file type, VEST would have to know a lot of your business in order to just make those decisions, if it's valid or not. And even if not, it would have to come with a lot of bloat that many users would not need. The same for email. I've been at places where, for example, email aliases are not allowed, like the plus after your username, and some places where it's only allowed for the own company's email addresses. And VEST cannot know this. So it gives you the decisions to make regarding business-specific validations. And the nice thing about VEST is that the assertions can be extended with custom frameworks and custom libraries. So for example, if you use a library that already does handle file type validation, you can just do enforce.extend and give it that input. And it will just work as any other validation inside of VEST. Very nice. Okay. Next question is, VEST has a functional interface. And usually when working with Vue, things are expected to be more reactive. Does VEST have a reactive interface? Oh, nice. So I just have to say, I'm coming from React background, originally. And I think that one of the nicest things about Vue is its reactivity model. I think it's really nice how things update and how things work this way. And the truth is that I do want to add a reactive interface to VEST. But it takes quite a lot of research, and I think it's something that VEST should have, or at least a bridge that would work with Vue's reactivity model. And this is something I would love to have some community contributions from. So if you know anything about Vue's reactivity model, or if you just want to learn about it and collaborate on it, I'm down. I think this is definitely the right audience for that. Another question from Organized Chaos. You mentioned you keep track of state internal to VEST. Have you exposed ways to clear that programmatically? Yes. Okay, I'll make it a bit longer. So VEST has multiple ways to clear the validation cache. So one is you can just discard the suite and just generate a new one, and you'll get a clear state. So just create a new suite. The other two ways are basically you can either do suite.reset. This is a function that when you call it, all the state gets wiped. And the other one, if you, for example, want to remove a specific field in case you have like dynamic validations, like the user added some item and now you have to run validations on it, and then the user removed it, then you can do suite.remove and specify the name of the field that you want to remove, and then this field alone gets removed. Usually you don't have to do either of those, but sometimes you do. And when you do, you have both reset and remove. Okay. Last question, but I feel like this one could be a long one. Why would I use VEST over vValidate or VULIDATE? Yes, yes. Thank you for that. So I just want to say that both these options are awesome. I mean, they have their reputation for a reason. They're good and they cater to the view audience, and they're very similar to many other frameworks and libraries that I've seen elsewhere. So if you're using them and they are working for you, you shouldn't switch. You should never switch a working technology when you have no good reason for it. But VEST does have some advantages when you do want to use it. And one, the syntax is pretty unique. So it borrows from somewhere else, and I think it makes it very easy to express stuff that in other frameworks are pretty complex. And this is something that VEST is really good at. And also VEST is separate from your UI logic. So if, for example, your app uses one framework and a different part of your app uses, like for example, React or I don't know what, you have like a multi-framework app or a micro-frontend, you can actually share your validations because you can use the same VEST functions, the same VEST suite. Also, if you have multi-platform, mobile or Node.js or whatever it is, you can just share stuff. So it's very easy because everything is separated. And this is something I think neither VValidate or VULidate give you. That is fair. Yeah. Yeah. All right. Well, great. Thank you so much, Eviatar. That was very enlightening.
21 min
20 Oct, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic