npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

graphql-validity

v0.10.12

Published

Make business logic validation easy on the graphql side without adding any declarations or modifications to the existing graphql schema.

Downloads

78

Readme

GraphQL Validity

What?

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.

Why?

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:

  1. A user adds a product to the cart through mutation and gets an OK response.
  2. User goes back to the store to check other stock you have, meanwhile someone orders the same product user has in the cart and it is out of stock.
  3. A user adds another product to the cart and gets an error, that his previous product is not available anymore. The problem is that we have to choose now, do we send an error to the user or we need to send success and then throw that error during checkout.

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:

  1. Adding validation errors/warnings to the schema itself - the WORST of them all, you simply mix data with metadata.
  2. Building functions compositions inside the resolve functions to replace resolver with validator which calls resolver after validation. - that can work, but first, you are complicating the code without any good reason. Second, you make graphql engine slower, especially if the wrapper function returns promise. And third your validation is scattered across the code and the more resolvers you have the more unneeded duplicate code you will generate. And last but not least the separation of concerns will hit you in the back, mixing things like auth, data sanity checks, and actual business logic is not the best you can do here.
  3. Put validation logic inside the resolve function along with business logic - it also can work, but has similar flaws as the point 2 above.

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.

How?

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.

Advanced configuration level

There is a certain level of validation for the schema:

  1. The first resolver encountered, checked only once: FieldValidationDefinitions['$'] - usually used for permissions validation.
  2. All of the resolvers available: FieldValidationDefinitions['*'] - rarely used, but still can be applied to something like input sanity checks for each field of the object.
  3. All the resolvers for a particular field: FieldValidationDefinitions['TestType'] - can be used to check all the fields of the given object
  4. Resolver for particular object field: FieldValidationDefinitions['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.