Makes adding custom business logic validation to graphQL easier
GraphQL Validity is a library for node.js that allows you to add a business logic validation layer to your BE, avoiding updates to your resolve functions and changing the look and fill with the graphql schema you are using.
This library originally was created for graphql-express
package, but also supports apollo-server
.
This library is a result of inconvenience for validation implementation using common graphql tools in js for the business logic.
The result of graphql operation is rather a value when the operation was successful or an error if the operation has failed. That works for basic cases, like permissions validation. You grab the field, you don
t have rights to see it and you get an error if you have rights the value is there. The problem comes, when you need to have ‘soft’ error inside the response along with value.
Imagine the following case:
This basic example might sound not scary enough overall, but one way or another application might get to the point where this can become a problem and app will have to bend the logic to comply the API.
Another issue this library tries to solve is the way validation happens for the business logic in the graphql.
Those are common patterns people use to handle the validation:
All of those can get the job done, but if you have a growing API, which is also subject to change, or you share validation patterns through different resolve functions, you can easily make your self a bad favor by applying one of those to your code.
This library allows you to declare all the validators separately from the schema implementation and keep it readable and usable.
It checks ease of maintenance, ease of change, and separation of concerns is also covered.
The most basic example usage:
//imports
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {
FieldValidationDefinitions,
wrapResolvers,
graphQLValidityExpressMiddleware
} = require('graphql-validity');
// get the schema object
const schema = require('./schema');
const app = express();
// Define validator functions
// each function should throw an error or return array of error objects or an empty array if everything is good
function applyGlobally(...args) {
return [new Error('Global failure!')];
}
// Define what to validate:
// $ will be checked ones, but for any resolver called
FieldValidationDefinitions['$'] = [applyGlobally];
// Wraps your resolvers schema with validators automatically
wrapResolvers(schema);
// Add middleware to express to make this lib work, as we need to connect schema validation
// results with request/response we got
app.use(graphQLValidityExpressMiddleware);
app.use('/graphql', graphqlHTTP((request) => {
return {
schema,
graphiql: true,
// Do not forget to pass your request as a rootValue,
// as we need to connect custom validation execution result with response
rootValue: request
}
}));
app.listen(4000);
To make it work you have to do the following things:
Import the code:
const {
FieldValidationDefinitions,
wrapResolvers,
graphQLValidityExpressMiddleware
} = require('graphql-validity');
Add validation function to a certain level of schema, by passing an array of functions doing the validation:
FieldValidationDefinitions['$'] = [applyGlobally];
Call the wrapResolvers function with your schema object as a parameter:
wrapResolvers(schema);
Add the middleware to your express instance, imported above:
app.use(graphQLValidityExpressMiddleware);
Add the express request object to the rootValue of your graphql express instance
app.use('/graphql', graphqlHTTP((request) => {
return {
schema,
graphiql: true,
// Do not forget to pass your request as a rootValue,
// as we need to connect custom validation execution result with response
rootValue: request
}
}));
The reason why this process has so many stages is that graphql instance resolution is separate from the actual express
work, so it adds a couple of additional stages I could not avoid when I was writing this library.
There is a certain level of validation for the schema:
FieldValidationDefinitions['$']
- usually used for permissions validation.FieldValidationDefinitions['*']
- rarely used, but still can be applied to something like input sanity checks for each field of the object.FieldValidationDefinitions['TestType']
- can be used to check all the fields of the given objectFieldValidationDefinitions['TestType:first']
- the example usage will be to have an object with public fields and a single private filed, which can be accessed only by the authorized person. For example user object which contains a password field, or personal address.