29

Let's say you have a form which posts data to API server. The API server validates the input and returns JSON object. If the input is invalid an error objects like the one below is returned.

{errors: {field1: "is required"}}

How do we handle and serve these kind of errors when using GraphQL? How and where should data validation be implemented (should that be part of GraphQL or should it be inside each resolve function)?

5 Answers 5

22

With validation logic inside the resolve method, you have complete control over the generated user errors. Here is an example:

// data/mutations/createUser.js
import {
  GraphQLObjectType as ObjectType,
  GraphQLNonNull as NonNull,
  GraphQLList as List,
  GraphQLString as StringType
} from 'graphql';
import validator from 'validator';
import UserType from '../types/UserType';

export default {
  type: new ObjectType({
    name: 'CreateUserResult',
    fields: {
      user: { type: UserType },
      errors: { type: new NonNull(new List(StringType)) }
    }
  }),
  args: {
    email: { type: new NonNull(StringType) },
    password: { type: new NonNull(StringType) }
  },
  resolve(_, { email, password }) {
    let user = null;
    let errors = [];

    if (validator.isNull(email)) {
      errors.push(...['email', 'The email filed must not be empty.']);
    } else if (!validator.isLength(email, { max: 100})) {
      errors.push(...['email', 'The email must be at a max 100 characters long.']);
    }

    // etc.

    return { user, errors };
  }
};

See my blog post on this subject - Validation and User Errors in GraphQL Mutations

Alternatively, create type UserErrorType { key: String!, message: String! } that can be used instead of plain strings when you compile the list of user errors to be returned to the caller.

GraphQL Query

mutation {
  createUser(email: "[email protected]", password: "Passw0rd") {
    user { id, email },
    errors { key, message }
  }
}

Query Response

{
  data: {
    user: null,
    errors: [
      { key: '', message: 'Failed to create a new user account.' },
      { key: 'email', message: 'User with this email already exists.' }
    ]
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is probably the best interim solution we're going to get until/unless GraphQL opts for a separate errors object to distinguish between system errors and user field/msg validation. The only slight change I've made is to create a dedicated ErrorType that has field and msg keys, so it's slightly easier to work with than having to care about array insert order... but it's ultimately the same thing.
You could also introduce a validation endpoint to the GraphQL schema itself, or a return a validations node within the results set.
1

It may be preferable to put the validation/capability checks into a service layer.

GraphQL is just one entry point into your application. Hence it shouldn't hold validation & capability checks.

If you think of an application that has multiple access layers (REST & GraphQL). You'll be duplicating code by adding validation checks in the GraphQL layer.

Best approach would be to have a code layer to handle this, e.g UserService. This would hold your logic for validation & capability checks.

GraphQL & REST API would just be formatters converting the response to the acceptable format for the respective response types. An example is below for illustration purposes:

class UserService {
    public function updateName(string $name) {
        // validation/capability check code here.
        // if validation fails, throw a user input exception or appropriate exception 
        //return value.
    }
}
GraphQl Mutation
class UserResolver {
    public function updateUserName(array $args, context $context) {
        try {
            $user = (new UserService() )->updateName(args['name']);
            return [
                'user' => $user
            ];
        } catch (UserInputException $exception) {
            return [
                'error' => $exception,
                'user' => null
            ];
        }
    }
}
REST API Controller
class UserController {
    public function updateUserName(string $name) {
        try {
            $user = (new UserService() )->updateName($name);

            return [
                'user' => $user
            ];
        } catch (UserInputException $exception) {
            return [
                'error' => $exception->message,
            ];
        }
    }
}

By using exceptions in the Service class this way, you can also select exceptions you want to be returned in your response(Can be a GraphQL or REST response).

We should only see GraphQL as an access layer. Resolver functions should be as dumb/simple as possible and not contain business logic, validations & capability checks.

Comments

0

Check this package out. It makes it easy to send machine-readable errors via the errors array on the graphql response. You can then feed the errors into your frontend and take action and/or alert the user to what has happened:

https://github.com/thebigredgeek/apollo-errors

Comments

0

I use a small package - graphql-validation to validate form in my project. It's wraps validator.js. So easy to use.

Example:

const { validator, validate } = require('graphql-validation'); // Import module

const resolver = {
  Mutation: {
    createPost: validator([ // <-- Validate here
      validate('title').not().isEmpty({ msg: 'Title is required' }),
      validate('content').isLength({ min: 10, max: 20 }),
    ], (parent, args, context, info) => {
      if (context.validateErrors.length > 0) {
        // Validate failed
        console.log(context.validateErrors); // Do anything with this errors

        return;
      }

      // Validate successfully, time to create new post
    }),
  },
};
Input: { title: '', content: 'Hi!' }

// console.log(context.validateErrors);
Output: [
  { param: 'title', msg: 'Title is required' },
  { param: 'content', msg: 'Invalid value' },
]

Hope it's useful.

Comments

0

I have created an npm module for handling validations in GraphQL in a better way. Please check the validate-graphql npm package.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.