HTTP status code handling in GraphQL APIs - graphql

A lot of resources say, that GraphQL should always respond with a 200 status code, even when an error occurred:
https://www.graph.cool/docs/faq/api-eep0ugh1wa/#how-does-error-handling-work-with-graphcool
https://github.com/rmosolgo/graphql-ruby/issues/1130#issuecomment-347373937
https://blog.hasura.io/handling-graphql-hasura-errors-with-react/
Because GraphQL can return multiple responses in one response, this makes sense. When a user requests two resources in one request, and only has access to the first resource, you can send back the first resource and return a forbidden error for the second resource.
However, this is just something I figured out along the way reading docs of multiple GraphQL libraries and blog posts. I didn't find anything about HTTP status codes in the offical specs, here https://spec.graphql.org/ or here https://graphql.org/
So I still have a few questions left:
Is it ok to return a HTTP 500 status code if I have an unexpected server error?
Is it ok to return a HTTP 401 status code, if credentials are wrong?
Should I include the potential HTTP status code inside the errors key of the GraphQL response like this
{
"errors" => [{
"message" => "Graphql::Forbidden",
"locations" => [],
"extensions" => {
"error_class" => "Graphql::Forbidden", "status" => 403
}
}]
}
Should I match common errors like a wrong field name to the HTTP status code 400 Bad Request?
{
"errors" => [{
"message" => "Field 'foobar' doesn't exist on type 'UserConnection'",
"locations" => [{
"line" => 1,
"column" => 11
}],
"path" => ["query", "users", "foobar"],
"extensions" => {
"status" => 400, "code" => "undefinedField", "typeName" => "UserConnection", "fieldName" => "foobar"
}
}]
}
I'd be great if you could share your experiences / resources / best practises when handling HTTP status codes in GraphQL.

GraphQL is transport-agnostic. While GraphQL services are commonly web services that accept requests over HTTP, they can and do accept requests over other transports as well. In fact, a GraphQL service can execute queries with no network requests at all -- all it needs is a query, and, optionally, a variables object and operation name.
Because of this, the GraphQL spec isn't concerned with methods, status codes or anything else specific to HTTP (it only mentions HTTP when discussing serialization). Any practices with regard to these things are at best conventions that have either evolved over time or are simply artifacts from some of the original libraries that were written for GraphQL. As such, any kind of answer to your question is going to be mostly based on opinion.
That said, because your GraphQL service shouldn't care about how its queries are received, arguably there should be a separation between its code and whatever code is handling receiving the requests and sending back the responses (like an Express app in Node.js). In other words, we could say it's never ok for your resolver code to mutate things like the response's status code. This is the current thinking in the community and most libraries only return one of two codes -- 400 if the request itself is somehow invalid and 200 otherwise.
If your entire GraphQL endpoint is guarded by some authentication logic (say your server checks for some header value), then a GraphQL request might come back with a 401 status. But this is something we handle at the web server level, not as part of your schema. It's no different if something went terribly wrong with your web server code and it had to return a 500 status, or the nginx server sitting in front of your returned a 494 (Request header too large), etc.
Traditionally, errors encountered during execution should be thrown and that's it. GraphQL extensions can be used to provide additional context when the errors are collected and serialized -- the name of the error, the stack trace, etc. However, it makes little sense to include HTTP status codes with these errors when, again, the errors have nothing to do with HTTP. Doing so unnecessarily mixes unrelated concepts -- if you want to identify the type of error, you're better off using descriptive codes like GENERIC_SERVER, INVALID_INPUT, etc.
However, conventions around error handling are also changing. Some services want to better distinguish client errors from other execution errors. It's becoming more common to see validation errors or other errors that would be shown to the end user to be returned as part of the data instead of being treated like an execution error.
type Mutation {
login(username: String!, password: String!): LoginPayload!
}
type LoginPayload {
user: User
error: Error
}
You can see payload types like these in action with public APIs like Shopify's. A variant on this approach is to utilize unions to represent a number of possible responses.
type Mutation {
login(username: String!, password: String!): LoginPayload!
}
union LoginPayload = User | InvalidCredentialsError | ExceededLoginAttemptsError
The end result is that the client errors are strongly typed and easily distinguishable from other errors that the end user doesn't care about. There's a lot of benefits to adopting these sort of conventions, but whether they are the right fit for your server is ultimately up to you.

Related

Apollo Client strips away additional results from response object

We have implemented our graphql api response like this.
{
data:  {...},
skip: 0,
limit: 10,
total: 100,
hasMore: true
}
If I query our api via graphiql the response looks like expected.
But unfortunately the apollo client in our application strips away all properties from the return object except data.
Is this expected behaviour?
And if so, how can I change it or solve this problem differently.
I need to get the total amount of data to implement pagination accordingly.
I know there is a method with fetchMore but it won't tell me the whole amount of entries in the list.
According to the spec only three top-level keys are expected -- data, errors and extensions. If you include additional keys you're going off-spec -- I would not expect any client to attempt to read them.
At the end of the day, this information should be included in your schema and returned as part of the data in the response. Returning it anywhere else (as additional keys in the response, as response headers, etc.) is a bad idea, if for no other reason than the fact that you could have multiple query fields at the root level, in which case you'd only be able to convey pagination information about one of the fields and it'd be unclear which field the information applied to. The same could be said if you have nested fields that can also be paginated.

Mixing of schema-level and app-level errors in GraphQL

While building a new application on top of a graphql API we have run into the following problem:
We have a mutation with an input field whose type is a custom scalar with its own validation rules (in this case that the input is a well-formed email address).
On the client, the user of the app fills in a bunch of fields and hits submit. Currently, validation of the email address is handled by the GraphQL layer and aborts the mutation if it fails with a top-level error. Validation of all other fields is handled by the mutation, returning app-level errors in the mutation payload. The other validations in this case cannot be represented directly in the schema since they involve inter-dependent fields.
This behaviour is really unhelpful for the client: it now has to know about errors in two possible locations (top-level graphql errors, and the application errors in the mutation payload) and in two possible formats. It also means that other malformed fields whose malformed-ness is not represented in the GraphQL schema will not be reported until all the schema-level issues have been fixed, forcing the user to go through multiple rounds of "fix the error, hit submit, get another error".
What is the standard solution to this problem? Putting validations (quite complex in this case) on the client? Weakening the schema in order to group all relevant validations at the application layer?
The problem with error categorization
top-level graphql errors, and the application errors in the mutation payload
The distinction that you made between schema-level and application level errors is based on GraphQL type and mutation implementation. A client-side application usually expects a higher abstraction level of errors, i.e., it needs to distinguish user errors and system errors. That way it can mask the system errors as "internal error" and present the user errors as necessary. The developer also can inspect the set of system errors.
See a nice and concise article by Konstantin Tarkus on this: Validation and User Errors in GraphQL Mutations, whose approach I have followed in this answer.
A Not-so-standard-yet-makes-sense solution
To the best of my knowledge, there is no particular standard approach. However, you can try out the following approach.
First, having system-level errors in the top-level field errors of mutation response:
{
"data": {
"viewer": {
"articles": {
"edges": [
{
"node": {
"title": "Sample article title",
"tags": null
}
}
]
}
}
},
"errors": [
{
"message": "Cannot read property 'bar' of undefined",
"locations": [
{
"line": 7,
"column": 11
}
]
}
]
}
Second, putting user-level errors as a separate field errors in mutation payload. Example from the mentioned article:
{
data: {
user: null,
errors: [
'',
'Failed to create a new user account.',
'email',
'User with this email address was already registered.',
]
}
}
// The errors field is just an array of strings that follows this pattern —
// [argumentName1, errorMessage1, argumentName2, errorMessage2, … ]
The above approach enables the client to look for user errors in a defined format in a single place - the errors field of mutation payload. It also allows the client to receive all errors together.
This approach loses automatic validation of the mutation's input type. However, validation is not compromised as the input type's validation logic can be put in a separate function. This function will return validation errors as necessary, which will eventually be put in mutation payload's errors field.
By the way, thanks for the well-written question!
If you are using Apollo, you can easily multiplex the errors array in the graphql response for both graphql errors AND custom errors that are machine readable using this package:
https://github.com/thebigredgeek/apollo-errors

How can I use apiary with JSON-RPC

I am trying to use apiary.io to document a JSON-RPC based API. I can get the pages formatted, but console simply does not work.
With JSON-RPC you typically only have 1 URI, such is the case with our API. Because of this, when attempting to define the methods, the blueprint editor gives the warning
Action with method POST already defined...
I figured I could ignore this, but in the apiary console when testing it will only returns the example response for the first action defined. Does anyone have a work around for this?
From what I understand from JSON-RPC spec and examples, multiple requests and responses could work for you better than defining POST endpoints multiple times.
# My API
## JSON-RPC [/endpoint]
### Doing something [POST]
+ Request Sum of numbers (application/json-rpc)
{"method": "sum", "params": {"a":3, "b":4}, "id":0}
+ Response 200 (application/json-rpc)
{"result": 7, "error": null, "id": 0}
+ Request Posting a message (application/json-rpc)
{"method": "postMessage", "params": ["Hello all!"], "id": 99}
+ Response 200 (application/json-rpc)
{"result": 1, "error": null, "id": 99}
Cons: Your API will be squashed into one or two endpoints and individual requests won't be visible in ToC.
Pros: The request-response pairing logic in Apiary mock server will then allow you to use some strategies (also described on the page linked above) to invoke different response than just the first one. However, as these strategies work only (in the time of posting this answer) with headers or status codes and they do not inspect body of incoming request's payload, you probably still won't be able to easily distinguish between your requests in console.
Possible workaround would be to give extra headers to your requests, such as X-Request: 1, X-Request: 2, etc., so the mock server can distinguish between them and return you the right response.
You can use trick with anchor, unique fragment path in api endpoint url.
# Group Awesnome JSON-RPC API
## Entity A [/#A]
### Procedure A [POST]
### Procedure B [POST]
## Entity B [/#B]
### Procedure C [POST]
### Procedure D [POST]

To 406 or not to 406 (http status code)

I'm developing a RESTful web application in Ruby with Sinatra. It should support CRUD operations, and to respond to Read requests I have the following function that formats the data according to what the request specified:
def handleResponse(data, haml_path, haml_locals)
case true
when request.accept.include?("application/json") #JSON requested
return data.to_json
when request.accept.include?("text/html") #HTML requested
return haml(haml_path.to_sym, :locals => haml_locals, :layout => !request.xhr?)
else # Unknown/unsupported type requested
return 406 # Not acceptable
end
end
Only I don't know what is best to do in the else statement. The main problem is that browsers and jQuery AJAX will accept */*, so technically a 406 error is not really the best idea. But: what do I send? I could do data.to_s which is meaningless. I could send what HAML returns, but they didn't ask for text/html and I would rather notify them of that somehow.
Secondly, supposing the 406 code is the right way to go, how do I format the response to be valid according to the W3 spec?
Unless it was a HEAD request, the response SHOULD include an entity containing a list of available entity characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection.
It looks like you're trying to do a clearing-house method for all the data types you could return, but that can be confusing for the user of the API. Instead, they should know that a particular URL will always return the same data type.
For my in-house REST APIs, I create certain URLs that return HTML for documentation, and others that return JSON for data. If the user crosses the streams, they'll do it during their development phase and they'll get some data they didn't expect and will fix it.
If I had to use something like you're writing, and they can't handle 'application/json' and can't handle 'text/html', I'd return 'text/plain' and send data.to_s and let them sort out the mess. JSON and HTML are pretty well established standards now.
Here's the doc for Setting Sinatra response headers.

Example Content-Types for `POST` validation failures?

Suppose an HTTP server responds to a POST with a 400 response code because the request failed validation (e.g. email address not found). If the server wishes to provide more information to the client about the nature of the error, how should this be returned? For each possible content type used in requests, should there ideally be an associated "error" content type?
For example, given the request
POST /users
Content-Type: application/x-myuser
{
"email": "foo#example.com",
"name": "Michael"
}
a response might be
400 Bad Request
Content-Type: application/x-myuser-error
{
"email": "Email address foo#example.com not found"
}
Are there any good examples of "error" content types publicly available?
I don't have any examples, but it's good to always keep these in mind:
Always include a machine-readable error, and generalize as much as possible. A JSON structure like
{"error":"Email address not found!","code":"fielderror","field":"email","reason":"notfound"} (could be simplified to {"error":"...","code":"emailnotfound"})
allows API developers to properly present the error to the user (and act on the error) while it allows you to change the messages without breaking applications. It also really helps with translation of error messages, both on your end and the external developer's end.
A different approach is to simply don't return any body, and use HTTP headers to tell the user agent what went wrong. For example, you could use X-Error and X-Error-Code to show a human readable error, and a machine readable code.
Creating too many content types might be a bad thing. I personally prefer to always use application/json and let the user agent know the status by looking at the HTTP codes: 200, 400, 403, 404, 500, etc.
Definitely don't ever start making combinations of HTTP codes and content types. You don't want your users to have to learn that application/myapp/error means there's an error, UNLESS it's 200 in which case you're in the edit screen, OR when it's 302 it's not actually an error but a redirect. This is why you should probably stick with one content type.
Bottom line: always keep it simple. Make sure that there's one field which you have to look at, not two or three, when detecting a status. Once the user agent has determined the status it could choose to look at some other fields for extra info, but only after it has determined that something went wrong. Including a separate content type probably won't help there.

Resources