javascriptnode.jspromisegraphqlexpress-graphql

How to return a rejected Promise to Graphql?


I've got a login method that returns a Promise to GraphQL to resolve. If the user is found and authenticated it resolves the user and works as it should. However, if the account doesn't exist or couldn't be resolved, the promise is rejected with a message Invalid credentials. This is fine but it seems I'm getting a GraphQL error when this happens so I think I'm sending GraphQL something its not expecting.

Here's my code: mutations.js:

login: {
  type: UserType,
  args: {
    email: { type: GraphQLString },
    password: { type: GraphQLString }
  },
  resolve(parentValue, { email, password }, req) {
    return AuthService.login({ email, password, req })
  }
}

AuthService.login:

function login({ email, password, req }) {
  return new Promise((resolve, reject) => {
    passport.authenticate('local', (err, user) => {
      if (!user) { reject('Invalid credentials.') }

      req.login(user, () => resolve(user));
    })({ body: { email, password } });
  });
}

The GraphQL error I'm getting back is this:

{
  "errors": [
    {
      "message": "Unexpected error value: \"Invalid credentials.\"",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "login"
      ]
    }
  ],
  "data": {
    "login": null
  }
}

But I think if I was returning a proper GraphQLFieldResolver wouldn't I get a much simpler error result? Like just an array or (in this case an array of one) error messages?

For example, lets say I change the resolver to this:

resolve(parentValue, { email, password }, req) {
  throw "Error!"
  // return AuthService.login({ email, password, req })
}

I'm getting the error message "message": "Unexpected error value: \"Error!\"". Shouldn't I be getting "message": "Error!" instead? Is that a GraphQL standard to return error messages with Unexpected error value: X?


Solution

  • The problem is that you're returning a promise that is rejected with a string, instead of an Error object. Or in your simpler version, you're throwing a string instead of an Error object.

    Just don't do that.

    I doubt there's something in the GraphQL standard about this (after all, this is the implementation not the API protocol), it's just graphql.js expecting Error objects (so that it can send stacktraces etc in development mode) and complaining about non-Error exceptions with an "Unexpected error value"message.

    To fix your code, use

    function login({ email, password, req }) {
      return new Promise((resolve, reject) => {
        passport.authenticate('local', (err, user) => {
          if (!user) { reject(new Error('Invalid credentials.')) }
    //                        ^^^^^^^^^^                      ^
    
          req.login(user, () => resolve(user));
        })({ body: { email, password } });
      });
    }
    

    or even better just reject(err).