Jacob Nuwamanya
4 min readAug 18, 2023

--

DESIGN YOUR SERVICE API, ENDPOINTS TO REFLECT CONTEXT & PURPOSE

photo credit: https://www.pexels.com/@thisisengineering/

I often spend time reading up on interesting projects, and looking into how they really work. It’s my version of a treasure hunt and I have learned a lot from my hunts.

Recently I came across the most interesting 404 Not Found thus far in my engineering career and would love to share it with any other treasure hunters out there.

Have you ever seen a 404 Not Found response returned for success and a 200 Success returned for an error?

You are probably confused and so was I for a while, in my humble opinion, I think the problem was two-fold;

  1. A clash of domains between English and Software Engineering.
  2. Faulty API design

I am going to break down this scenario and hopefully by the end of this article, you will learn;

  • How to design your endpoints to answer the right questions being asked of your API.
  • How to choose the right status codes, think about the context of your endpoints and how to structure your responses.

Let’s get started.

Three service A, B and C. A is a third party service that processes some information and generates ids, B checks if the id generated by A is unique in the database before it can register that id along with some information and C is only called into action if service B successfully registers information.

Service B only expects unique ids from Service A, however, it is not guaranteed. A combination of factors could in rare cases cause a duplication.

Part one of the problem — Clash of domains

Under normal circumstances, we don’t expect an ID generated by service A to be found in the database of service B.

Does that imply it’s an error and should service B respond with a 404 Not Found error response when asked to verify if the ID is unique?

To answer the question above, we need to first agree upon the definition of an error.

Imagine you leave your laptop at your desk, and when you return, it is missing. That situation calls for concern, because something that should be found is NOT FOUND.

But if you walked away from your desk, leaving nothing behind and finding nothing upon return, would that be a problem, a cause for concern?

In both cases, your laptop was NOT FOUND at your desk, but the context is different. In one there was an expectation of your laptop being at your desk, not finding it there is an error, which isn’t the same for case 2, therefore..

An error is an unexpected occurrence

In the English language domain, you would be right to say your laptop was NOT FOUND at your desk, however, in the software engineering domain, NOT FOUND is a type of error and as such it can only be used if the result of a process is an error.

The question that you should then ask yourself first is, is this an error, an unexpected occurrence?, which leads us to …

Part two of the problem — Faulty API design.

Brief reminder of the architecture described above, A generates ids, B checks if they are unique and C executes only if ids are unique.

If you think about this context, when do we have an error in execution?

I bet you answered “when B receives a duplicate (not unique) id” Service B not finding the passed id is not an error because that is expected behaviour, and this shouldn’t stop execution, however, having a duplicated id is an error and execution should stop.

Therefore it is appropriate to respond with an error status code if an id is FOUND in the database because this is an unexpected occurrence, a conflict.

Unfortunately, in this particular API by design, B would return a 404 Not Found whenever it searched for the passed in id and couldn’t find it. Essentially it returned an error for expected behaviour and a success for the unexpected behaviour.

This might seem trivial until you realise a few things;

  1. You can’t use a monitoring tool on such an API for it will flag errors that are not really errors and ignore errors that should not be ignored.
  2. You can’t onboard a new developer to work on that API without special instructions to ignore some errors because they are not really errors.
  3. Debugging is complicated because each error is questioned and inspected first to ascertain if it really is an error or not before proceeding. Heck you might as well inspect the success status for some errors.
  4. Error logs can’t be trusted or relied upon which further complicates the debugging process and many more other reasons.

If you find yourself giving special instructions about your API for example, “ignore that error” then there is something fundamentally wrong with the design, for errors are not meant to be ignored nor used as identifiers for successful processing.

Solution.

The issue seemed to start with the endpoint ‘single-id’ on service B. That endpoint did not accurately reflect the question that the service was expected to answer, which is check if the ID is unique so;

Change endpoint to accurately reflect the question the service was intended to answer from ‘single-id’ to ‘confirm-unique-id’

By making that simple change, the purpose becomes apparent, if id is unique meaning it is not found in the database, then return 200 successful, else return status code 409 Conflict.

HTTP 409 error status indicates that the request could not be processed because of a conflict in the request or the result of processing the request would create a conflict within the resource.

An error for unexpected behaviour and a success for expected behaviour, no special API instructions.

Thank you for sparing the time to read this & I hope to see you again till then you can find me on linkedIn and medium.

--

--

Jacob Nuwamanya

I build digital products with Nuxt -Vue -Node -Android & enjoy the thrill of learning new things.