BattlefyBlogHistoryOpen menu
Close menuHistory

3 bad rest endpoint designs

Ronald ChenMarch 28th 2022

While REST defines useful constraints, there are still plenty of way to shoot yourself in the foot. Let’s consider an use-case and attempt to write a REST endpoint for it. We’ll strawman some bad designs in order to get to a final good one.

Sample use-case

Let’s say we are tasked to implement an endpoint that allows users to delete their accounts. However, due to some manual business processes, user accounts cannot be deleted immediately. The actual business process is:

  1. User requests account to be deactivated (👈🏻 we are only designing this one)
  2. Support staff perform deactivation procedure
  3. Sometime later, user account is deleted

We are only designing an endpoint for step 1.

Attempt #1, delete the user

One of the design principles of REST is to use the HTTP semantics, so let’s just use the DELETE method!

DELETE /users/:userID
content-length: 0

Unfortunately, this design is for an endpoint to delete the user, whereas we want to request a deactivation. This would be a suitable endpoint design when we are actually ready to delete the user after the deactivation process has been completed.

Further, if this endpoint did exist and but the user account has not been deactivated yet, it should error with:

400 BAD REQUEST
content-type: text/plain
Unable to delete user. User account must be deactivated first.

Attempt #2, put the user into a deactivating state

OK fine. Let’s add a state field on the user and let it be an enum of active, deactivating or deactivated. Then we can PATCH the state to be deactivating.

PATCH /users/:userID
content-type: application/json
{
  "state": "deactivating"
}

This is better, but this design ends up making the user resource a god object, in which we keep adding new features to a single resource. It would be more maintainable if features could be added more independently.

God objects also tend to bloat the GET of a resource or worst leak sensitive information if public & private data are stored in the same place.

The ability to PATCH the state also implies a user could change their state back to active. A guard would need to be implemented to prevent a user from updating their state after the deactivation process has begun, as that would otherwise violate the business process.

Lastly, this API style is passive. The mental model here is by updating the state, some other process will detect/query for update.

Attempt #3, send deactivate command

Fine! Let’s make write an active command to simply deactivate the user.

POST /users/:userID/deactivate
content-length: 0

This is again better, but semantically this means “deactivate now”. Only a small tweak to get to the final solution.

Final solution, send request deactivation command

POST /users/:userID/request-deactivation
content-length: 0

This may seem like a pedantic distinction, but this is why naming things is hard. Human languages are imprecise and we need to put in extra effort naming things. A good name is the first step towards understanding and ensuring the intended functionality is maintained.

Holup, but isn’t POST only for creating new resources in REST? REST tells us to write semantic HTTP, so let’s see what the HTTP spec says about POST.

POST: Perform resource-specific processing on the request payload.

POST can be used to submit commands to resources.

Do you want write semantic HTTP? You’re in luck, Battlefy is hiring.

2022

Powered by
BATTLEFY