Reputation: 4238
I'm having a trouble with Graphql and Apollo Client.
I always created different responses like 401 code when using REST but here I don't know how to do a similar behavior.
When I get the response, I want it to go to the catch function. An example of my front-end code:
client.query({
query: gql`
query TodoApp {
todos {
id
text
completed
}
}
`,
})
.then(data => console.log(data))
.catch(error => console.error(error));
Can anybody help me?
Upvotes: 28
Views: 46507
Reputation: 1931
There has been a recent addition to the spec concerning errors outputs:
GraphQL services may provide an additional entry to errors with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementors to add additional information to errors however they see fit, and there are no additional restrictions on its contents.
Now using the extensions
field you can custom machine-readable information to your errors
entries:
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
}
]
}
Latest version of Apollo-Server is spec-compliant with this feature check it out, Error Handling.
Upvotes: 11
Reputation: 1
I think a missing concern in the discussion about graphql and errors, are errors in the transformation from http to gql, and this is oftwn where a 401 should occour.
When transforming the request, you should transform the Authorization header (or whatever authentication method you are using) to a user and if it cannot be authenticated, it should return a HTTP 401 error - this is not a part of the graph or the spec for you api, and is just a matter of whether the user can be validated or not. You don't even have to inspect the query.
On the other hand a 403 error will most likely occour in the gql layer (and will probably not use the http status code, but that is another discussion), since it can be very domain specific and you have to know the query to decide whether it is forbidden or not.
A HTTP 403 status could be used to tell the user that he cannot access the gql api at all.
We solve this issue in express/nestjs by having a middleware before hitting the graphql layer that enriches the request with the user (maybe undefined) or fails if the user cannot be authenticated. I don't think 401 should ever be returned if you don't provide credentials (or the like).
Upvotes: 0
Reputation: 5854
After experimenting with this for a bit, I realized that some important details are missing. Mainly, if you have a custom error object with custom fields, the above examples will allow you to read your custom properties because it appears that custom errors are casted into a standard Error
object with only a message property.
Here is what my formatError
function looks like (note the originalError
property):
app.use('/graphql', auth.verifyAccess, graphqlHTTP((req, res) => {
return {
schema: makeExecutableSchema({
typeDefs: typeDefs,
resolvers: rootResolver
}),
graphiql: true,
formatError: (err) => ({
message: err.originalError.message || err.message,
code: err.originalError.code || 500
}),
}
}));
The originalError
prop seems to always be set but as a safeguard you could use lodash get
property.
And I have a defined custom error class called APIError
class APIError extends Error {
constructor({ code, message }) {
const fullMsg = `${code}: ${message}`;
super(fullMsg);
this.code = code;
this.message = message;
}
}
export default APIError;
In my resolvers, I throw exceptions as such:
const e = new APIError({
code: 500,
message: 'Internal server error'
});
Upvotes: 1
Reputation: 86
Just to complement Glenn's answer, here is the part of Graphql Spec that defines how errors should be handled. So to know if the request failed (or partially failed) your can check for the "errors" key at the root of the response.
Upvotes: 2
Reputation: 7172
The way to return errors in GraphQL (at least in graphql-js) is to throw errors inside the resolve functions. Because HTTP status codes are specific to the HTTP transport and GraphQL doesn't care about the transport, there's no way for you to set the status code there. What you can do instead is throw a specific error inside your resolve function:
age: (person, args) => {
try {
return fetchAge(person.id);
} catch (e) {
throw new Error("Could not connect to age service");
}
}
GraphQL errors get sent to the client in the response like so:
{
"data": {
"name": "John",
"age": null
},
"errors": [
{ "message": "Could not connect to age service" }
]
}
If the message is not enough information, you could create a special error class for your GraphQL server which includes a status code. To make sure that status code gets included in your response, you'll have to specify the formatError
function when creating the middleware:
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema: myGraphQLSchema,
formatError: (err) => ({ message: err.message, status: err.status }),
}));
Upvotes: 40