How should you Respond to a HTTP DELETE?

If you’re building a RESTful API, you’ll soon come across the scenario where you need to delete some resource, so you make an endpoint with the DELETE verb. Someone calls that endpoint, and you have some options for what to return:

  • HTTP 202 – the entity is not deleted but you’ve enqueued the request to delete the item, and the system will get to it.
  • HTTP 200 – the entity is deleted and you need to return some entity to the caller (maybe some kind of receipt).
  • HTTP 204 – the entity is deleted and there’s nothing to return.

Now onto the final scenario: the entity doesn’t exist. How do you respond? The options are universally argued to be between 204 and 404.

You can get into this situation in a couple of ways:

  1. The resource never existed.
  2. Multiple requests to delete the resource were sent, which can be for a variety of reasons, race conditions being the one that can’t reasonably be prevented.

When thinking of the difference between 204 and 404, I try to think of what the response code tells the user:

  • 204 says “the call was a success: the system does not contain the item you requested for deletion”
  • 404 says “the call was not a success: the system did not contain the item you requested for deletion”

When comparing them like that, a 404 doesn’t make sense. It is my contention that the 404 scenario is, instead, vacuously successful, and should be a 204. Whether the data was deleted in a race with another caller, deleted in a previous call that failed to return a response (prompting the client to retry), or succeeded vacuously because the data wasn’t there to begin with, it doesn’t so much matter. The request to have the resource deleted was successful. Whether or not the server had to do work to actually delete anything is irrelevant, and a 404 probably breaks the abstraction that the endpoint’s contract provides.

Those are my thoughts. Hope it helps.

Query-by-POST

Just a quick one today.

When trying to implement a REST endpoint that does some filtering, it’s generally pretty easy and obvious. Just add filters as query string parameters. For example:

GET /api/employees?lastName=Smith

… and the response should be an HTTP 200, with a collection of employees with the last name Smith. Standard fare. You can continue to add filtering parameters and it’s all really straightforward.

What about if you wanted to query for all employees named Smith, that started before 01/01/2019? That’s more like a search than a filter. For searching, a common pattern exists that some of my peers and I have come to call “Query-by-POST”. I can’t seem to find decent documentation on it, so I’m doing that now. It looks something like the following:

POST /api/employees/searches
{
"lastName" : "Smith",
"hireDate" : {
"lessThan" : "01/01/2019"
}
}

… and the response is:

HTTP/1.1 303 See Other
Location: https://.../searches/results/<id>

That is, you’re POSTing a new search to the API, and the API is returning a redirect to the results it created.

The Id of the search results can be anything you want. Ideally, it should actually represent some kind of resource. I’ve used an encoded list of the ids from the search result, and it worked well. If it’s computationally expensive, then you can persist the results as any other resource as well. In order for the endpoint to be RESTful though, you should get back the same resource (results) each time you call the results endpoint.

Under inspection, one thing probably looks odd: you’re POSTing to the searches collection and redirecting to the search results resource… instead of just returning search results to the original request. That’s twice as many HTTP Requests as when you POST to create any other resource. Here’s why…

Normally, when you POST a resource to an endpoint like this:

POST /api/employees
{
“id” : “”,
“firstName” : “Jane”,
“lastName” : “Smith”,
“hireDate” : “12/30/2018”
}

You will often get back a 201 with the employee object that contains the populated Id. You’re POSTing the object to the same collection that it will be located at.

With the search endpoint, you are actually creating a request for the system to create search results for you. If it were going to return anything in the body to that endpoint, it could reasonably return your search object (with the lastName and hireDate comparison) as well as the Location header, and it would be idiomatically RESTful. Because it’s actually/logically creating a resource somewhere else, it redirects you to it.

Hope it helps!