The other one is http status codes are http status codes. As in the http request was done correctly, but the application code wasn't. More specifically, http layer was executed successfully, but the application layer was not.
No, this pattern is not the right one for REST. Don't call it REST, call it command/action RPC. The 90s called - they want their architecture back.
It's pretty infuriating actually. On a similar note I have to use certain command line tools provided by a third party vendor that exits 0 on failure, and writes something to STDERR (on success it exits 0 and writes something to STDOUT). The Unix conventions evolved over decades because they were consistent and useful.
But there's an rfc and that's what defines correct usage.
I used to use HTTP status codes in this way because I understood it was the correct REST way of doing things.
However, one day, a sysadmin contacted me to tell me that we had broken a release because our API was returning a 404. Actually it was a problem in the checking script that was checking for data that was no longer in the DB.
By making application codes equal to HTTP status codes, we had removed any way to distinguish between fatal errors and API results.
There was no server-side error. The requested resource was unable to be located because it no longer existed. You should return a 404 here, and not just for non-HTML API clients.
Application-side fatal errors are in the 500 block. So yes, you absolutely can distinguish this case.
I should have mentioned that the API was behind a reverse proxy. This leads to the following questions:
Which requested resource is missing? The API itself or the item requested from the API? How does a client distinguish between these?
If the API itself was not found that’s one of 502, 503 or 504.
200 responses with an error property is an antipattern precisely because of things like reverse proxies. They have no way to unpack and interpret every developers pet error format. They _do_ understand http status codes and can act accordingly, like retrying requests, not caching responses, etc as appropriate.
What if the configuration changes and the API path/URL is no longer in the reverse proxy config? What if the reverse proxy is dynamically configured and our application didn't register itself properly?
They _do_ understand http status codes and can act accordingly, like retrying requests, not caching responses, etc as appropriate.
Of course applications should return HTTP codes when appropriate e.g 500. The principle is that using HTTP codes for application specific information e.g item not found in DB, is a blunt instrument.
They have no way to unpack and interpret every developers pet error format.
I would argue that they don't need to. The conversation is between client and API at higher level than HTTP. HTTP codes are great for information such as "its broken", "please authenticate", "its busy". But HTTP codes are not so useful for things such as "item not in db", "parameter x is missing", "unsupported API version" because this information is nothing to do with HTTP.
2. You can return a proper HTTP status and a JSON response body detailing what happened.
GraphQL defines the format of the reponse in case of errors[1]
GraphQL doesn't use HTTP status codes to communicate out-of-the-ordinary conditions. You can expect to always get HTTP status 200[1]
The data response is a mirror of the query you sent in with the data present[2]
How to query for data is explicitely laid out[3]
How to send in parameters is explicitely laid out[4]
[1] https://graphql.org/learn/serving-over-http/ [2] https://graphql.org/learn/ [3] https://graphql.org/learn/queries/#fields [4] https://graphql.org/learn/queries/#variables
We do have nice wrappers around the request to raise a proper named exception regardless so it doesn’t matter.
Edit: actually you made me think a little bit more about this, if you can make your mutations idempotent, spurious retried POST requests shouldn't be a problem at all. However "delete the last record" is not an idempotent operation by definition but also one you wouldn't use in the real world - usually you delete by ID.
Edit 2: it's easy to make the server reject mutations sent via GET.
For graphql specifically, if you allow GET-ing the GraphQL endpoint (which usually isn't the case by default), it's trivial to ensure only queries go through that method.
how to cache data (POST is not cacheable) -> You can use GET requests and GET requests are cacheable.
how to auth data (anyone has access to everything) -> Authentication or authorization? What do you mean with anyone has access to everything?
how to... -> yes?
GET requests are a crutch added to GraphQL precisely because of limitation of POST requests.
And the backend still has to normalise the GET request, and possibly peek inside it to make sure that it is the same as some previous request.
> how to auth data (anyone has access to everything) -> Authentication or authorization? What do you mean with anyone has access to everything?
Your schema is a single endpoint with all the fields you need exposed. Oh, but a person X with access Y might not have access to fields A, B, C, and D.
Too bad, these fields can appear at any level of the hierarchy in the request, deal with it.
> how to... -> yes?
A GraphQL query is ad-hoc. It can have unbounded complexity and unbounded recursion. Ooops, now you have to build complexity analysers and things to figure out recursion levels.
A GraphQL service usually collects data from several external services and/or a database (or even several databases). But remember, a GraphQL query is both ad-hoc and with potential unbounded complexity. Oh, suddenly we have to think how much data and at what time to we retrieve, how do we get the data without retrieving too much, and without hammering the external services and the database with thousands of extra requests.
That's just from the top of my head.
Ans so you end up with piles of additional solutions of various quality and availability on top of GraphQL servers and clients: caching, persisted queries etc. etc.
How are GET requests a crutch? If anything GraphQL is completely agnostic to which HTTP method you use to access it. You don't even have to run GraphQL over HTTP, it can work over MQTT, NATS, telnet...
> And the backend still has to normalise the GET request, and possibly peek inside it to make sure that it is the same as some previous request.
Which is what any caching proxy must do anyway?
> Your schema is a single endpoint with all the fields you need exposed. Oh, but a person X with access Y might not have access to fields A, B, C, and D.
In your GraphQL implementation you can just deny fulfilling requests that contain fields person X doesn't have access to. This problem is not limited to GraphQL, it's a generic authorization problem.
> A GraphQL query is ad-hoc. It can have unbounded complexity and unbounded recursion. Ooops, now you have to build complexity analysers and things to figure out recursion levels.
You don't have to build a complexity analyzer or figure out recursion levels, there are already tools that do that for you. But you can go another way and just create a list of approved queries.
> A GraphQL service usually collects data from several external services and/or a database (or even several databases)
Usually? That's just speculation. And that's entirely on the implementation of that service, it has nothing to do with GraphQL spec/technology itself.
They were not in the original spec IIRC. URL's are limited in legth (it's not in the spec, but most clients have a limit) etc.
> Which is what any caching proxy must do anyway?
Nope. A caching proxy can benefit from HTTP Cache Headers [1]. But cache headers don't work well with GraphQL's GET requests, and don't work at all with the default, which is POST.
> This problem is not limited to GraphQL, it's a generic authorization problem.
GraphQL makes it significantly more complex though. Because your requests are ad-hoc.
> You don't have to build a complexity analyzer or figure out recursion levels, there are already tools that do that for you.
Indeed. By adding more and more complexity. And no, tools only solve a part of the problem. Simply a dataloader on a server doesn't entirely solve the N+1 problem.
> But you can go another way and just create a list of approved queries.
Turning it into REST with none of the benefits of REST.
> Usually? That's just speculation. And that's entirely on the implementation of that service
It's not speculation. That's the main use case for GraphQL. But even if you just slap it on top of a single database, you still have the problem of ad-hoc queries hammering your database.
They were not in spec because the spec doesn't say anything over which medium it should be transported. In fact the spec [1] only mentions the word HTTP 5 times: 4 times in example data and one time discussing implementation details when sending data over HTTP. GraphQL can't be faulted for the limits of the transport over which it is used.
> Nope. A caching proxy can benefit from HTTP Cache Headers [1]. But cache headers don't work well with GraphQL's GET requests, and don't work at all with the default, which is POST.
How do cache headers not work well with GraphQL GET requests? That is entirely up to the server that implements the API. If that server doesn't implement caching well, that's not GraphQL's fault.
> It's not speculation. That's the main use case for GraphQL. But even if you just slap it on top of a single database, you still have the problem of ad-hoc queries hammering your database.
The main use case of GraphQL is any two things that want to exchange data with each other. Merging data from multiple data sources as its main use case is simply not true. The ability of GraphQL to merge different data sources is one of its abilities but it's not intrinsic to GraphQL.
> Turning it into REST with none of the benefits of REST.
And what exactly are those benefits? I'm here defending GraphQL yet none of the downsides of REST are being taken into account. GraphQL brings structure where there was none, that alone is a significant reason to choose GraphQL to structure your API.
> N+1 problem
There are tools like Postgraphile that solve this. It converts your GraphQL query into one efficient database query.
> ad-hoc queries hammering your database
And what prevents anyone from hammering a REST API? GraphQL doesn't release the developer from implementing sane constraints - something that has to happen with any API implementation and not specific to GraphQL.
If not the spec, then original documentation. GET is a late add-on.
> How do cache headers not work well with GraphQL GET requests?
In REST:
- a resource is uniquely identified by it's URI
- when the server sends back cache headers, any client in between (any proxies, the browser, any http clients in any programming language etc.) can and will use these cache headers to cache the request
In GraphQL GET:
- http://myapi/graphql?query={user{id,name}} and http://myapi/graphql?query={user{name,id}} are two different requests
- it gets worse for more complex queries, especially if they are dynamically constructed on the client
- each of those is viewed as a separate query with separate caching
- cache normalisation and query normalisation are a thing in the graphql world (and non-existent in REST) because of that.
That's a yet another layer of complexity that you have to deal with
> And what exactly are those benefits? I'm here defending GraphQL yet none of the downsides of REST are being taken into account.
I wish anyone was willing to discuss the downsides of GraphQL. Bashing REST is the norm, but GraphQL is the holy grail that accepts no criticism.
Benefits of REST over GraphQL, off the top of my head:
- it's HTTP, plain and simple. So everything HTTP has to offer is directly available in REST. See this HTTP decision diagram [1]
- caching doesn't require you to normalise and unpack every single request and response just to figure out if something is cached
- You know your requests, so you can provide optimised queries, resolution strategies, necessary calls to external services as required by the call
> There are tools like Postgraphile that solve this. It converts your GraphQL query into one efficient database query.
I'd love to see that proven for any sufficiently complex and large database.
> And what prevents anyone from hammering a REST API?
No ad-hoc queries prevents anyone from hammering a REST API that you can specifically tune to the specific request and data you need.
GraphQL requires significantly more care especially if you're not running it on just one database. And even then, oops, joins: https://news.ycombinator.com/item?id=25014918
And we're back to requiring the graphql server to be able to limit recursion depth, query complexity, etc. etc.
[1] https://github.com/for-GET/http-decision-diagram/tree/master...