Back to learn

Building end-to-end form validation with Yup, graphql-shield and GraphQL

by Henry Kirkness16 November 2022 3 Min Read

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

Building Your Form Schema

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:

const CreateUpdateTodoItem = yup.object().shape({
id: yup.number().nullable(),
message: yup
.max(100, "You can enter a maximum of 100 characters")
.required("Please enter a message")

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:

import { inputRule, shield } from "graphql-shield";
const createUpdateTodoItemRule = inputRule(() => CreateUpdateTodoItem);
const schema = applyMiddleware(
makeExecutableSchema({ /* ... */ }),
shield({ Mutation: { createUpdateTodoItem: createUpdateTodoItemRule } })

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:

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.


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!

I'm the techy-co-founder at Planes. I love working with our developers and clients to solve technical challenges, whether that's through hands-on coding or coaching and support.
Copied to clipboard!
We think you might like
Get more fresh ideas like this in your inbox
Get more fresh ideas like this in your inbox
Pages loaded on average in 1.0s, emitting
~0.46g of CO2
Let's shake things up
For clients
CJ Daniel-Nield
For careers
Sophie Aspden
People Lead
Everything else
Say hello
Drop us a line