3

Let's say we have an API with a route /foo/<id> that represents an instance of an object like this:

class Foo:
    bar: Optional[Bar]
    name: str
    ...

class Bar:
    ...

(Example in Python just because it's convenient, this is about the HTTP layer rather than the application logic.)

We want to expose full serialized Foo instances (which may have many other attributes) under /foo/<id>, but, for the sake of efficiency, we also want to expose /foo/<id>/bar to give us just the .bar attribute of the given Foo.

It feels strange to me to use 404 as the status response when bar is None here, since that's the same status code you'd get if you requested some arbitrarily incorrect route like /random/gibberish, too; if we were to have automatic handling of 404 status in our client-side layer, it would be misinterpreting this with likely explanations such as "we forgot to log in" or "the client-side URL routing was wrong".

However, 200 with a response-body of null (if we're serializing using JSON) feels odd as well, because the presence or absence of the entity at the given endpoint is usually communicated via a status rather than in-line in the body. Would 204 with an empty response-body be the right thing to say here? Is a 404 the right way to go, and if so, what's the right way for the server to communicate nuances like "but that was a totally expected and correct route" or "actually the foo-ID you specified was incorrect, this isn't missing because the attribute was un-set".

What are the advantages and disadvantages of representing the missing-ness of this attribute in different ways?

Glyph
  • 31,152
  • 11
  • 87
  • 129

3 Answers3

2

I wonder if you could more clearly articulate why a 200 with a null response body is odd. I think it communicates exactly what you want, as long as you're not trying to differentiate between a given Foo not having a bar (e.g. Foo.has_key?(bar)) and Foo having a bar explicitly set to null.

jayofdoom
  • 36
  • 1
  • I've updated the question to include this information, "because the presence or absence of the entity at the given endpoint is usually communicated via a status rather than in-line in the body", but this *is* the solution that we went with, which ultimately seemed the most practical. So I think you have a point; but I wonder if you could expand on the benefits of doing it this way! – Glyph Jul 29 '21 at 08:22
  • 1
    I wish I could give you a citation, or a real technical reason, but the honest answer is I spent two years working at a company helping reverse engineer point of sale system APIs. This made me appreciate APIs that are explicit (e.g. `200` with `null`) over those that tried to be overly clever with response codes and implicit information. – jayofdoom Jul 29 '21 at 14:04
  • OK I think I'm going to accept this one, just because `null` *is* a perfectly valid JSON object. The analogy that I eventually came up with was this: `application/json` is a media type. `image/svg` is also a media type. `null` is a perfectly valid JSON document. Is it valid to return `` as an SVG document with a 200? Seems clear that it is; so why not `null` in JSON? Thanks to everyone for the perspective here! – Glyph Jul 30 '21 at 23:00
1

Of 404, https://developer.mozilla.com says,

In an API, this can also mean that the endpoint is valid but the resource itself does not exist.

so I think it's acceptable. 204 doesn't strike me as particularly outlandish in this situation, but is more commonly associated (IME, at least) with DELETEs (and occasionally PUTs/POSTs that don't return results.)

swizzard
  • 1,037
  • 1
  • 12
  • 28
  • Either seems acceptable, I'm pretty sure all the answers are *right* here, but what would you say are the benefits of communicating the status via a `204` or `404`? What practical problems might arise from `200`/`null`? – Glyph Jul 29 '21 at 08:23
1

I also struggle a lot with this because:

  1. 404 can point to a non existent url, or a path that is acceptable but the particular referenced resource does not exist. I have also used it to error out on request body's that carry identifiers that are non existent.

  2. A lot of people shoe-horn these errors into the bad request (400) error code which is somewhat acceptable but also a cop out. (Literally anything the server did not process successfully can be classified as a bad request, if you think about it)

  3. With 2(above) in mind, a 400 with some helpful message body is sometimes used to wash out the guilt of not committing outrightly to a 404, but this demands some parsing expectations on the client's side, which is not always nice. Also returning a 400 which, according to this is kind of gaslighting the client, because 400 errors are supposed to be the client's fault entirely with regard to the structure of the request, not because the client asked for something not in your db.

400 Bad Request response status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

  1. The general feeling is that 200 means all is good, and therefore there's always a tacit expectation the response will always contain some form of body, not null.(Right??) I wouldn't encourage using a 200 for these situations. While 204's don't carry the responsibility having to carry a response body, they also sort of convey the message that "something worked", which is not the message you want to send here, right?

What I'm trying to say? Thoughtful API design is hard.

Thuita Wachira
  • 176
  • 2
  • 16
  • Very much vibing with "kind of gaslighting the client" on a 400 status code :). There's no way for the client to know *in advance* to properly formulate its query so as not to get a `null` - that's the server's job! But I can also see why this cop-out is appealing. – Glyph Jul 30 '21 at 22:56