We, very roughly, do it like this:
- 200: all good
- 401: we don't know who you are
- 403: you're not allowed to do that
- 400: something's wrong and you can fix it
- 500: something's wrong and you can't fix it
Each response (other than 401) includes a json blob with details that our UI can do something with, but any other consumer of the API or HTTP traffic still knows roughly what's going on.
I've worked in places where we really sweated on getting the perfect HTTP status codes, and I'm not sure it added much benefit.
On POST - I find myself doing logical GETs with POST a lot, because the endpoint requires more information than can be conveyed in URL params. It makes me feel dirty, and it's obviously not RESTful but you know - sometimes you just have to get things done.
The fact that existing tooling relies on some of these conventions is probably a good enough reason to do things this way, but it's not obvious to me that this is because it's actually better rather than inertia. Conventions could be developed around the body of requests as well, and at least to me, it doesn't seem obvious that the amount of information conveyed at the HTTP method/response status layer was necessary to try to separate from the semantics of the request and response bodies. I'm sure that a part of that was due to HTTP supporting different content types for payloads, but nowadays it seems like quite a lot of the common alternatives to JSON APIs were designed not to even use HTTP (GraphQL, gRPC, etc.), which I'd argue is evidence that HTTP isn't necessarily being used as well for APIs as some people would like.
To make something explicit that I've been alluding to, everything I've said is about using APIs in HTTP, not HTTP in the context of viewing webpages in a browser. It really seems like a lot of the complications in HTTP are due to it trying to be sufficient for both browsers and APIs, and in my opinion this comes mostly at the expense of the latter.
It seems you have some alternative in mind but it wasn't presented.
...until you discover an intermediate later strips the body of certain error codes. Like apache, which IIRC includes all 5xx and most 4xx.
Also that is not a controversial take. It is at best a naive or inexperienced take.
I use http status codes to encode how the _request_ was handled, not necessarily the data within the request.
A 400 if you send mangled JSON, but a 200 if the request was valid but does not pass business validation rules.
Inside the 200 response is structured JSON that also has a status that is relevant at the application level.
Otherwise how can for example you tell if a 404 response is because the endpoint doesn't exist, or because the item requested at the endpoint doesn't exist?
I believe it's important to have a separation between what is happening at the API level vs Application, and this approach caters for both.
What about empty required field in JSON? Is it still mangled or it's already BL?
200 OK {status: "failed", errors: ["field X is required"]}
How you deal with this on the application side, what JSON statuses you have etc is up to you.
For example, on a server I'm working on there are helper functions that generate different types of responses. Responding in certain ways will produce a 200, but will also log a warning or error.
On the client side, you can create request helpers that all requests go through and that can resolve requests appropriately, rendering error messages to the user etc.
The main thing is to have a well defined, consistent approach.
We've been doing that for 20 years with json-rpc 1.0
--> { "method": "echo", "params": ["Hello JSON-RPC"], "id":1}
<-- { "result": "Hello JSON-RPC", "error": null, "id": 1}
In this context, HTTP is just the transport and HTTP errors are only transport errors.Yes, you throw away lots of HTTP goodies with that, but there are many situations where it makes more sense than some half-assed ReSTish API. YMMV.
json-rpc is probably what your describing over http, maybe if you squint enough graphql too
It's is certainly not comprehensive, but it's right there and it works.
Moving to your own solution means that you have to reimplement all of this in every client.
Please elaborate. In my experience, most of HTTP client libraries do not automatically retry any requests, and thank goodness for that since they don't, and can't, know whether such retries are safe or even needed.
> redirection
An example of service where, at the higher business logic level, it makes sense to force the underlying HTTP transport level to emit a 301/302 response, would be appreciated. In my experience, this stuff is usually handled in the load-balancing proxy before the actual service, so it's similar to QoS and network management stuff: the application does not care about it, it just uses TCP.
The same holds true if you are using any programming library: there is a plethora of handlers for HTTP errors.
As for redirection, a common example is offering downloads through S3 using pre-signed URLs (you share a URL with your own domain, but after auth redirect to a pre-signed S3 URL for direct download or upload).
You don't need to reinvent HTTP semantics when HTTP is already there, standard, doing the right thing, compatible with millions of programs all across the stack, out of the box.
HTTP is so well designed it almost makes me angry when people try to sidestep it and inevitably end up causing pain in the future due to some subtle semantic detail that HTTP does right and they didn't even think to reimplement.
And the only solution to such issues (as they arise, and they will) is to slowly reimplement HTTP across the whole stack: oh, you need to monitor your internal server errors? Now you have to configure your monitoring tool (or create your own) to inspect all your response bodies (no matter how huge) and parse their JSON (no matter how irrelevant) instead of just monitoring the status code in the response header and easily ignore the expensive body parsing.
Even worse when people go all the way. If we don't need status codes, why do we need URLs at all? Just POST everything to /api/rpc with an `operation` payload. Congrats, none of your monitoring tools can easily calculate request rates by operation without some application-specific configuration (I wish this was a made up scenario).
Just use HTTP ffs. You'd need a very good reason not to use it.
4xx or 5xx gets you the default HTTP handling for that kind of error. Occasionally - especially in small examples - that default handling does what you want and saves you duplicating a lot of work. More often it gets in your way.
I'd compare it to browser default styling - in small examples it sounds useful, but in a decent-sized site you just end up having to do a "CSS reset" to get it out of the way before you do your styling.
Shoehorning http protocols error codes as application error codes, drinking the cool aid and calling it best practice is beyond bizzare.
500 is "failed to do anything at all"