Skip to main content

To POST, or PUT, PATCH, and DELETE? URLs are cheap, but API design matters

Published on

Last week, there was a trending hot take on social media: why bother with HTTP methods beyond GET and POST in your API? The argument was that URLs are cheap, so why not create more API URLs rather than using the same URL with different HTTP methods? It was a very interesting thought process around API design because it is the opposite of my thinking. I am a fan of RESTful semantic APIs and using HTTP methods to adequately describe the action an endpoint is performing on a resource. I am also an API design nerd, hence this blog post.

Generally, HTTP methods represent the following actions:

  • GET retrieves data.
  • POST creates data.
  • PUT updates data entirely.
  • PATCH allows partially updating data.
  • DELETE removes data.

PATCH was introduced as a new HTTP method in RFC 5789: PATCH Method for HTTP. I have not come across an API implementing both PUT and PATCH. Generally the API uses one or the other. PUT requires updating all data properties, whereas PATCH allows updating only specific properties. If you haven't done a deep dive into HTTP methods, I recommend reading the MDN documentation or RFC 9110: HTTP Semantics.

The example that piqued my interest involved an API for reservations. Let's model an API for a reservation resource using each HTTP method. Then, we'll review what it'd look like only using GET and POST.

The API has two paths for accessing reservation resources: /reservations and /reservations/{id}.

  • GET /reservations retrieves all reservations for the user.
  • GET /reservations/{id} retrieves a user's specific reservation.
  • POST /reservations creates a new reservation for the user.
  • PATCH /reservations/{id} to change the time or number of people for the user's reservation.
  • DELETE /reservations/{id} to remove a user's reservation.

While I would defer to using PATCH so that the time number of people can be modified individually, the API could require PUT instead. Changing either value could be interpreted as replacing the reservation, essentially replacing the reservation resource without changing its identifier.

Now, let's envision this API using only GET and POST methods

  • GET /reservations retrieves all reservations for the user.
  • GET /reservations/{id} retrieves a user's specific reservation.
  • POST /reservations creates a new reservation for the user.
  • POST /reservations/{id}/change to change the time or number of people for the user's reservation.
  • POST /reservations/{id}/cancel to remove a user's reservation

The API is mostly the same until we reach the endpoints that modify or delete the reservation. Now we have a /reservations/{id}/change and /reservations/{id}/cancel paths. Not that big of a change. The API client needs to use a few different paths versus HTTP methods.

However, let's remember what API stands for application programming interface. The emphasis is on "interface." An API is an interface between two different systems. Interfaces are intended to be contracts to encapsulate logic so that the two systems can be decoupled. My concern about the latter approach with paths such as /reservations/{id}/change and /reservations/{id}/cancel is that they are specific. You may think I'm splitting hairs, but we must assume the software we write will outlast us and the original requirements.

An argument I read against DELETE /reservations/{id} in favor of POST /reservations/{id}/cancel was the internal state of the reservation resource. Does deleting a reservation delete the resource permanently? Or is it marked as canceled and still accessible in the list of reservations? If the reservation is canceled, is it really deleted? This exact discussion is why I prefer using HTTP methods in my APIs. 

What if the behavior wasn't explicitly outlined in the architecture or user stories? Matching API resource endpoint operations against HTTP methods brings up discussions when they're not entirely aligned. To me, anything that performs a hard or soft delete still applies to the DELETE method. That is logic specific to the backend and encapsulated.

In this example I would add DELETE /reservations, which allows deleting canceled reservations in bulk. This

URLs may be cheap. But they come at a design cost when building your API.

I'm available for one-on-one consulting calls – click here to book a meeting with me 🗓️

#