In this post, I cover a relatively specific but common problem in modern web applications: form validation.
I go through how you can set up a system to use your existing front-end form validation to validate your GraphQL mutations/queries. This means that if a user were to send a GraphQL query direct to your API, they'd run into the same business logic as is implemented in your form, and you don't need to write it twice!
As a side-effect of how specific this post is, you'll need to have an understanding of the following:
GraphQL
graphql-shield
yup
We'll be building a simple todo input that is validated by its length and limited to 100 characters. Our yup schema could look as follows:
1const CreateUpdateTodoItem = yup.object().shape({2id: yup.number().nullable(),3message: yup4.string()5.max(100, "You can enter a maximum of 100 characters")6.required("Please enter a message")7});
Right now you might write this logic twice, once in a yup schema on the front-end, and perhaps in your resolver or model logic on the backend. This becomes pretty hectic when your form logic becomes complex and needs to be maintained by others.
Tip: If you are using React, I highly recommend using Formik, it works seamlessly with yup.
Adding to your GraphQL validation is as simple as your existing front-end implementation, you'll write a new graphql-shield rule for each query/mutation that you want validated. For example, in our case:
1import { inputRule, shield } from "graphql-shield";23const createUpdateTodoItemRule = inputRule(() => CreateUpdateTodoItem);45const schema = applyMiddleware(6makeExecutableSchema({ /* ... */ }),7shield({ Mutation: { createUpdateTodoItem: createUpdateTodoItemRule } })8);
The schema you write for end-to-end validation may need to differ slightly from the schema you use only on the front-end. The reason being that quite often your form fields don't map 1-2-1 to your GraphQL arguments. The solution for this is to write the validation as if it were to be used only on the backend, and transform the input from your form using:
1CreateUpdateTodoItem.transform(formInput => { /* validated object */ });
You can share code between applications in more ways than one, however I'd always recommend using Yarn Workspaces to manage the dependencies of your front-end, back-end and shared code, all in one monorepo. You could alternatively publish a private npm package.
Although this may be overkill for some projects, in production apps one of the most common security vulnerabilities is the lack of back-end input validation. Making it easier for yourself by reusing the code will result in a more secure system.
This is just one solution with some relatively specific tools, I hope this at least helps kickstart your thinking into how you might implement in your own project!