HTTP status code handling in GraphQL APIs

一曲冷凌霜 提交于 2020-01-30 08:03:27

问题


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:

  1. Is it ok to return a HTTP 500 status code if I have an unexpected server error?
  2. Is it ok to return a HTTP 401 status code, if credentials are wrong?
  3. 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
    }
  }]
}
  1. 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.


回答1:


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.



来源:https://stackoverflow.com/questions/59729656/http-status-code-handling-in-graphql-apis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!