Building End-To-End Form Validation with Yup, graphql-shield and GraphQL

By Henry Kirkness09/20/2018 2 Min Read — In Development

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. Meaning that if a user was to send a GraphQL query direct to your API, they'd run into the exact 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

Building Your Form Schema

We'll be building a simple todo input that is validated by it's length and limited to 100 characters. Our yup schema could look as follows:

1
const CreateUpdateTodoItem = yup.object().shape({
2
id: yup.number().nullable(),
3
message: yup
4
.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.

Your Backend Implementation

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:

1
import { inputRule, shield } from "graphql-shield";
2
3
const createUpdateTodoItemRule = inputRule(() => CreateUpdateTodoItem);
4
5
const schema = applyMiddleware(
6
makeExecutableSchema({ /* ... */ }),
7
shield({ Mutation: { createUpdateTodoItem: createUpdateTodoItemRule } })
8
);

Things To Note

  • 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:

1
CreateUpdateTodoItem.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.

Conclusion

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!

Need a hand with a React, React Native or GraphQL project?

© 2020 Planes. All rights reserved.