RESTful Alternatives to DELETE Request Body
Asked Answered
P

5

121

While the HTTP 1.1 spec seems to allow message bodies on DELETE requests, it seems to indicate that servers should ignore it since there are no defined semantics for it.

4.3 Message Body

A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.

I've already reviewed several related discussions on this topic on SO and beyond, such as:

Most discussions seem to concur that providing a message body on a DELETE may be allowed, but is generally not recommended.

Further, I've noticed a trend in various HTTP client libraries where more and more enhancements seem to be getting logged for these libraries to support request bodies on DELETE. Most libraries seem to oblige, although occasionally with a little bit of initial resistance.

My use case calls for the addition of some required metadata on a DELETE (e.g. the "reason" for deletion, along with some other metadata required for deletion). I've considered the following options, none of which seem completely appropriate and inline with HTTP specs and/or REST best practices:

  • Message Body - The spec indicates that message bodies on DELETE have no semantic value; not fully supported by HTTP clients; not standard practice
  • Custom HTTP Headers - Requiring custom headers is generally against standard practices; using them is inconsistent with the rest of my API, none of which require custom headers; further, no good HTTP response available to indicate bad custom header values (probably a separate question altogether)
  • Standard HTTP Headers - No standard headers are appropriate
  • Query Parameters - Adding query params actually changes the Request-URI being deleted; against standard practices
  • POST Method - (e.g. POST /resourceToDelete { deletemetadata }) POST is not a semantic option for deleting; POST actually represents the opposite action desired (i.e. POST creates resource subordinates; but I need to delete the resource)
  • Multiple Methods - Splitting the DELETE request into two operations (e.g. PUT delete metadata, then DELETE) splits an atomic operation into two, potentially leaving an inconsistent state. The delete reason (and other related metadata) are not part of the resource representation itself.

My first preference would probably be to use the message body, second to custom HTTP headers; however, as indicated, there are some downsides to these approaches.

Are there any recommendations or best practices inline with REST/HTTP standards for including such required metadata on DELETE requests? Are there any other alternatives that I haven't considered?

Polymerism answered 14/1, 2013 at 17:45 Comment(1)
Certain implementations like Jersey does not allow body for delete requests.Keheley
P
61

Despite some recommendations not to use the message body for DELETE requests, this approach may be appropriate in certain use cases. This is the approach we ended up using after evaluating the other options mentioned in the question/answers, and after collaborating with consumers of the service.

While the use of the message body is not ideal, none of the other options were perfectly fitting either. The request body DELETE allowed us to easily and clearly add semantics around additional data/metadata that was needed to accompany the DELETE operation.

I'd still be open to other thoughts and discussions, but wanted to close the loop on this question. I appreciate everyone's thoughts and discussions on this topic!

Polymerism answered 18/3, 2013 at 16:36 Comment(5)
This is a bad idea. One place where this will get you in trouble is if you later decide to use an HTTP acceleration service like Akamai EdgeConnect. I know for a fact that EdgeConnect strips bodies from HTTP DELETE requests(since they consume bandwidth are are likely invalid). It's also likely that similar services do the same(see Kindle's acceleration feature, and other CDN-like services). You should probably redesign to not use HTTP verbs for your service. Most APIs make little sense using HTTP-verbs/classical-REST and HTTP verb transport issues are very hard to troubleshoot.Marta
I concur with @Gabe, sending a body with methods which have no body by definition is a sure-fire way to randomly lose data as your bits traverse the internet pipes, and you'll have a very difficult time debugging it.Hoptoad
These comments against using the DELETE are irrelevant until they address the very valid issues that the OP has. I'm trying my best to adhere to the spirit of REST, but a delete without optimistic concurrency and a delete that doesn't have an atomic batch operation isn't practical in real-life situations. This is a serious deficiency with the REST pattern.Telemachus
I'm with @Quarkly on this. I don't understand what the RESTFUL idea is on how we should check concurrency etc. Concurrency checks do not belong on the client.Cumberland
https://mcmap.net/q/53084/-is-an-entity-body-allowed-for-an-http-delete-request cites lists.w3.org/Archives/Public/ietf-http-wg/2020JanMar/0123.html in which Roy Fielding says "a body cannot change the meaning of a received request. They are absolutely forbidden to have any impact whatsoever on the processing or interpretation of the request". In your case, the body impacts the processing. I have a similar problem and my "reason for delete" can be arbitrarily long Unicode text. I may use DELETE as well--because it's crazy that I can't delete something with DELETE--but I think POST is truer to the spec.Keynes
A
12

Given the situation you have, I would take one of the following approaches:

  • Send a PUT or PATCH: I am deducing that the delete operation is virtual, by the nature of needing a delete reason. Therefore, I believe updating the record via a PUT/PATCH operation is a valid approach, even though it is not a DELETE operation per se.
  • Use the query parameters: The resource uri is not being changed. I actually think this is also a valid approach. The question you linked was talking about not allowing the delete if the query parameter was missing. In your case, I would just have a default reason if the reason is not specified in the query string. The resource will still be resource/:id. You can make it discoverable with Link headers on the resource for each reason (with a rel tag on each to identify the reason).
  • Use a separate endpoint per reason: Using a url like resource/:id/canceled. This does actually change the Request-URI and is definitely not RESTful. Again, link headers can make this discoverable.

Remember that REST is not law or dogma. Think of it more as guidance. So, when it makes sense to not follow the guidance for your problem domain, don't. Just make sure your API consumers are informed of the variance.

Amargo answered 15/1, 2013 at 5:37 Comment(7)
Regarding the use of query params, my understanding is that the query is part of the Request-URI per section 3.2, and therefore using this approach (or likewise, the separate endpoints) goes against the definition of the DELETE method, such that "resource identified by the Request-URI" is deleted.Polymerism
The resource is identified by the uri path. So a GET to /orders/:id would return the same resource as /orders/:id?exclude=orderdetails. The query string is only giving hints to the server - in this case to exclude orderdetails in the response (if supported). Similarly, if you are sending DELETE to /orders/:id or /orders/:id?reason=canceled or /orders/:id?reason=bad_credit, you are still acting on the same underlying resource. To keep a 'uniform interface,' I would have a default reason so that sending the query param is not required.Amargo
@Polymerism You are right in your concerns about query strings. The query string is part of the URI. Sending a DELETE request to /foo?123 means you are deleting a different resource than if you were to send DELETE to /foo?456.Hoptoad
@Amargo Sorry, but much of what you say is wrong. The resource is identified by the entire URI, not just the path. Different query strings are different resources (in the HTTP sense of the word 'resource'). Also, a default reason isn't required for a uniform interface. That term refers to using GET, PUT, POST, PATCH and DELETE they way HTTP defined them. The commonality is between vendors (user agent vendors, API vendors, caching proxy vendors, ISPs etc) and not within one's own API (though that too should be uniform in design for the sanity of it's users!).Hoptoad
@Nicholas I don't understand your need to argue a discussion that ended three years ago. The answer and the comments I made are valid and correct from a REST-centric view. REST is not HTTP (nor any vendor implementation of HTTP). In the context of REST, the resources are the same. And as I said in my answer, REST is not law or dogma, but guidance.Amargo
Apologies CP. I didn't spot the year on those comments, and thought they were only a week old! — I returned here because someone voted on my answer and I couldn't remember this particular question, so came to re-read it. I still assert that your comments re. query strings and what constitutes a uniform interface are incorrect, but I frequently also tell people "REST is not law or dogma, but guidance", pointing them to articles such as web.archive.org/web/20140917143640/http://nordsc.com/ext/… and letting them choose what's best for them.Hoptoad
@nicholas Thanks. I appreciate the apology. Our answers were very similar. Our styles are a bit different. All good things. :)Amargo
H
12

What you seem to want is one of two things, neither of which are a pure DELETE:

  1. You have two operations, a PUT of the delete reason followed by a DELETE of the resource. Once deleted, the contents of the resource are no longer accessible to anyone. The 'reason' cannot contain a hyperlink to the deleted resource. Or,
  2. You are trying to alter a resource from state=active to state=deleted by using the DELETE method. Resources with state=deleted are ignored by your main API but might still be readable to an admin or someone with database access. This is permitted - DELETE doesn't have to erase the backing data for a resource, only to remove the resource exposed at that URI.

Any operation which requires a message body on a DELETE request can be broken down into at it's most general, a POST to do all the necessary tasks with the message body, and a DELETE. I see no reason to break the semantics of HTTP.

Hoptoad answered 15/1, 2013 at 9:10 Comment(2)
What happens if PUT reason succeeds and DELETE resource fails? How can inconsistent state be prevented?Hanoi
@Hanoi the PUT only specifies the intent. It can exist without a corresponding DELETE, which would indicate that someone wanted to delete but either it failed or they changed their minds. Reversing the order of the calls would also allow DELETEs to occur without a reason — provision of a reason would then be considered merely as annotation. It is for both of these reasons that I would recommend using option 2 from the above, i.e. changing the state of the underlying record such as to cause the HTTP resource to disappear from it's current URL. A garbage collector/admin can then purge recordsHoptoad
A
1

I would say that query parameters are part of the resource definition, thus you can use them to define the scope of your operation, then "apply" the operation. My conclusion is that Query Parameters as you defined it is the best approach.

Aronow answered 12/10, 2022 at 13:56 Comment(0)
S
-1

I suggest you include the required metadata as part of the URI hierarchy itself. An example (Naive):

If you need to delete entries based on a date range, instead of passing the start date and end date in body or as query parameters, structure the URI such a way that you pass the required information as part of the URI.

e.g.

DELETE /entries/range/01012012/31122012 -- Delete all entries between 01 January 2012 to 31st December 2012

Hope this helps.

Seraglio answered 15/1, 2013 at 7:7 Comment(4)
Doesn't cover cases like sending a reason for deletion i.e. commment field.Sweetmeat
Wow. thats a terrible idea. If you have too much of meta data, It will bloat the size restrictions on URI.Selfesteem
This approach does not follow RESTful practices and is not recommended as you'll have a convoluted URI structure. Endpoints get muddled with intertwined resource identification vs meta data and in time will become a maintenance nightmare as your API changes. It's much more preferred to have the range specified in query params or payload which is the meat of this question: to understand the best practices approach to the problem which I would say is not this.Schwarzwald
@Schwarzwald - I do not understand what you mean by convoluted URI structure? Also, this is a HTTP DELETE so payload is not an option but query parameters yes.Seraglio

© 2022 - 2024 — McMap. All rights reserved.