Validation and User Errors in GraphQL Mutations
In this blog post I would like to propose the pattern which you can use to handle user input validation and user errors in your GraphQL API server. It’s based on the assumption that there is a clear distinction between system and user errors. For example, if you send a query to a GraphQL mutation endpoint with some required input arguments omitted, you would get a response similar to this one:
{
errors: [
{ message: 'Filed "createUser" argument "email" of type
"String!" is required but not provided' }
]
}
This type of errors is not supposed to be displayed to end users. It helps with debugging, error tracking etc. But it’s considered to be exceptional, only happening if there is a bug or some critical run-time issue in your program.
It is often required to perform additional validation of the input parameters passed to GraphQL mutations, and provide user-friendly localized error messages in case validation fails or mutation cannot be completed successfully.
Each of these error messages should contain the name of the input argument that didn’t pass validation and the reason why it failed. It would also be helpful to support multiple error messages per input field, as well as error messages that are not associated with any particular field.
So, here is how the schema of a GraphQL mutation may look like:
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload
}type CreateUserInput {
email: String!
password: String!
}
type CreateUserPayload {
user: User
}
And here is an example GraphQL query for it:
mutation M {
createUser(input: {
email: "hello@tarkus.me",
password: "Passw0rd"
}) {
user { id email }
}
}
If the mutation fails, the GraphQL response to this query may look as follows:
{
errors: [{
message: 'Request is invalid.',
path: ['createUser'],
state: {
'': ['Failed to create a new user.'],
email: ['A user with this email already exists.']
},
...
}]
}
The state field is just an object with keys representing the names of input parameters that didn’t pass server-side validation and an array of error messages for each of these keys. There is also a special case ''
field that may contain one more errors that are not related to any particular input field.
In your client app, you can now grab this info, highlight the form fields that caused these errors and ask the user to fix errors and submit the form again.
Here is how a JavaScript implementation of this GraphQL mutation with form validation may look like:
The mutation code above validates the input by using validator.js library, builds an array of error messages and throws a custom error before saving data into the database. Let’s see how this ValidationError
class looks like:
Note: This syntax only works if don’t compile classes into ES5 code.
The purpose of this class is to reshape the list of error messages into format that can be easily consumed on the client.
And at last, you would need to tweak express-graphql
middleware to include this custom errors into a response.
app.use('/graphql', expressGraphQL(req => ({
...
formatError: error => ({
message: error.message,
state: error.originalError && error.originalError.state,
locations: error.locations,
path: error.path,
}),
})));
I hope this helps. Happy coding!
For a complete example visit Node.js API Starter Kit on GitHub.