ChatGPT解决这个技术问题 Extra ChatGPT

Delete multiple records using REST

What is the REST-ful way of deleting multiple items?

My use case is that I have a Backbone Collection wherein I need to be able to delete multiple items at once. The options seem to be:

Send a DELETE request for every single record (which seems like a bad idea if there are potentially dozens of items); Send a DELETE where the ID's to delete are strung together in the URL (i.e., "/records/1;2;3"); In a non-REST way, send a custom JSON object containing the ID's marked for deletion.

All options are less than ideal.

This seems like a gray area of the REST convention.


N
Nicholas Shanks

Is a viable RESTful choice, but obviously has the limitations you have described. Don't do this. It would be construed by intermediaries as meaning “DELETE the (single) resource at /records/1;2;3” — So a 2xx response to this may cause them to purge their cache of /records/1;2;3; not purge /records/1, /records/2 or /records/3; proxy a 410 response for /records/1;2;3, or other things that don't make sense from your point of view. This choice is best, and can be done RESTfully. If you are creating an API and you want to allow mass changes to resources, you can use REST to do it, but exactly how is not immediately obvious to many. One method is to create a ‘change request’ resource (e.g. by POSTing a body such as records=[1,2,3] to /delete-requests) and poll the created resource (specified by the Location header of the response) to find out if your request has been accepted, rejected, is in progress or has completed. This is useful for long-running operations. Another way is to send a PATCH request to the list resource, /records, the body of which contains a list of resources and actions to perform on those resources (in whatever format you want to support). This is useful for quick operations where the response code for the request can indicate the outcome of the operation.

Everything can be achieved whilst keeping within the constraints of REST, and usually the answer is to make the "problem" into a resource, and give it a URL. So, batch operations, such as delete here, or POSTing multiple items to a list, or making the same edit to a swathe of resources, can all be handled by creating a "batch operations" list and POSTing your new operation to it.

Don't forget, REST isn't the only way to solve any problem. “REST” is just an architectural style and you don't have to adhere to it (but you lose certain benefits of the internet if you don't). I suggest you look down this list of HTTP API architectures and pick the one that suits you. Just make yourself aware of what you lose out on if you choose another architecture, and make an informed decision based on your use case.

There are some bad answers to this question on Patterns for handling batch operations in REST web services? which have far too many upvotes, but ought to be read too.


It's not your server that you have to worry about, it's intermediaries, CDNs, caching proxies, etc. The internet is a layered system. That is the reason it works so well. Roy determined which aspects of the system were necessary for its success, and named them REST. If you issue a DELETE request, whatever lies between the requestee and the server will think a single resource, at the specified URL, is being deleted. Query strings are opaque parts of the URL to these devices, so it doesn't matter how you specify your API, they are not privy to this knowledge so cannot behave differently.
/records/1;2;3 will not work if you have a lot resources to delete due to URI length restrictions
Note that if considering DELETE and a body defining the resources to purge, that some intermediaries may not forward the body. Also, some HTTP clients cannot add a body to a DELETE. See stackoverflow.com/questions/299628/…
@LukePuplett I would simply state that passing a request body with a DELETE request is forbidden. Don't do it. If you do I will eat your children. Nom nom nom.
The problem with argument for #3 is that it carries the same penalty as counter argument against #2. Creating to-delete resource is not something that upstream proxies will know how to handle - the same counter argument that is raised against approach #2.
M
Martin Ždila

If GET /records?filteringCriteria returns array of all records matching the criteria, then DELETE /records?filteringCriteria could delete all such records.

In this case the answer to your question would be DELETE /records?id=1&id=2&id=3.


I also came to this conclusion: just flip the verb to what you want to do. I don't understand how what goes for GET does not go for DELETE.
GET /records?id=1&id=2&id=3does not mean “get the three records with IDs 1, 2 & 3”, it means “get the single resource with URL path /records?id=1&id=2&id=3” which might be a picture of a turnip, a plain text document containing the number "42" in chinese, or may not exist.
Consider the following: two sequential requests for /records?id=1 and /records?id=2 are sent, and their responses cached by some intermediary (e.g. your browser or ISP). If the internet knew what your application meant by this, then it stands to reason that a request for /records?id=1&id=2 could be returned by the cache simply by merging (somehow) the two results it already has, without having to ask the origin server. But this is not possible. /records?id=1&id=2 might be invalid (only 1 ID allowed per request) or may return something completely different (a turnip).
This is a basic resource caching problem. If my DBA mutated the state directly, then caches are now out of sync. You give an example 410 returned by the intermediary, but 410 is for permanent removals, upon DELETE a cache might clear its slot for that URL, but it won't send a 410 or a 404, since it doesn't know if a DBA hasn't just put the resource immediately back again at origin.
@NicholasShanks I really disagree. If the results are cached, that's the fault of the server. And if you're talking about the design of the API, you're hopefully the one writing the code for the server. Whether you use id[]=1&id[]=2 or id=1&id=2 in the query string to represent an array of values, that query string does represent just that. And I think it is extremely common & good practice to have the query string represent a filter. Besides, if you allow for deletes and updates, don't cache the GET requests. If you do, clients will hold stale state.
b
bootsoon

I think Mozilla Storage Service SyncStorage API v1.5 is a good way to delete multiple records using REST.

Deletes an entire collection.

DELETE https://<endpoint-url>/storage/<collection>

Deletes multiple BSOs from a collection with a single request.

DELETE https://<endpoint-url>/storage/<collection>?ids=<ids>

ids: deletes BSOs from the collection whose ids that are in the provided comma-separated list. A maximum of 100 ids may be provided.

Deletes the BSO at the given location.

DELETE https://<endpoint-url>/storage/<collection>/<id>

http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions


This seems like a good solution. I guess if mozilla thinks it is correct then it must be? The only question then is error handling. Suppose they pass ?ids=1,2,3 and id 3 does not exist do you delete 1 and 2 then respond with a 200 because the requester wants 3 gone and it is not there so it does not matter? or what if they are authorized to delete 1 but not 2 ... do you delete nothing and respond with an error or do you delete what you can and leave the others...
I typically will return a successful response because the end state is the same regardless. This simplifies logic on the client as well since they no longer have to handle that error state. As for the authorization case, I would just fail the whole request...but really it depends on your use case.
I concur with Nathan.
F
Felix K.

This seems like a gray area of the REST convention.

Yes, so far I have only come accross one REST API design guide that mentions batch operations (such as a batch delete): the google api design guide.

This guide mentions the creation of "custom" methods that can be associated via a resource by using a colon, e.g. https://service.name/v1/some/resource/name:customVerb, it also explicitly mentions batch operations as use case:

A custom method can be associated with a resource, a collection, or a service. It may take an arbitrary request and return an arbitrary response, and also supports streaming request and response. [...] Custom methods should use HTTP POST verb since it has the most flexible semantics [...] For performance critical methods, it may be useful to provide custom batch methods to reduce per-request overhead.

So you could do the following according to google's api guide:

POST /api/path/to/your/collection:batchDelete

...to delete a bunch of items of your collection resource.


Is a viable solution that the list of items is communicated via a JSON formatted array?
yes sure. you can POST a payload in which the ids are sent via a json array.
It is interesting that Google API guide said If the HTTP verb used for the custom method does not accept an HTTP request body (GET, DELETE), the HTTP configuration of such method must not use the body clause at all, at Custom Method chapter. But the GET accounts.locations.batchGet api is GET method with body. That's is weird. developers.google.com/my-business/reference/rest/v4/…
@鄭元傑 agree, looks a bit weird on the first sight but if you look closely it is actually a POST http method used and only the custom method is named batchGet. I guess google does it to (a) stick with their rule that all custom methods need to be POST (see my answer) and (b) to make it easier for people to put a "filter" in the body so you do not have to escape or encode the filter as with query strings. the downside, of course, is that this is not really cacheable anymore...
@deamon - Why not? The custom operation IS the resource in this case. And you are POSTing to it which, as the Google API Guide author pointed out, has the most flexible semantics. While ReST APIs often look like thin CRUD wrappers, this isn't a requirement.
L
Luke Puplett

I've allowed for a wholesale replacement of a collection, e.g. PUT ~/people/123/shoes where the body is the entire collection representation.

This works for small child collections of items where the client wants to review a the items and prune-out some and add some others in and then update the server. They could PUT an empty collection to delete all.

This would mean GET ~/people/123/shoes/9 would still remain in cache even though a PUT deleted it, but that's just a caching issue and would be a problem if some other person deleted the shoe.

My data/systems APIs always use ETags as opposed to expiry times so the server is hit on each request, and I require correct version/concurrency headers to mutate the data. For APIs that are read-only and view/report aligned, I do use expiry times to reduce hits on origin, e.g. a leaderboard can be good for 10 mins.

For much larger collections, like ~/people, I tend not to need multiple delete, the use-case tends not to naturally arise and so single DELETE works fine.

In future, and from experience with building REST APIs and hitting the same issues and requirements, like audit, I'd be inclined to use only GET and POST verbs and design around events, e.g. POST a change of address event, though I suspect that'll come with its own set of problems :)

I'd also allow front-end devs to build their own APIs that consume stricter back-end APIs since there's often practical, valid client-side reasons why they don't like strict "Fielding zealot" REST API designs, and for productivity and cache layering reasons.


I loved this answer right up until I read the last sentence :) I have never seen a use case where applying strict REST has had a net detrimental effect. Sure it can make for more code to write at both ends, but you end up with a safer, cleaner, less coupled system.
Haha. It's actually become a pattern! Backend for front-end it's called on ThoughtWorks technology radar. It also allows more application logic to be written that would be cumbersome in say, JavaScript, and obviously can be updated without a client so update say, for an iOS app.
Skim-reading the first four hits from Google, it seems that this BFF technique can only work when the clients are under your control. The client developers develop the API they want, mapping calls to microservice APIs that are the real back end. In this diagram: samnewman.io/patterns/architectural/bff/#bff I would place the "Perimeter" line below the BFF boxes — each box is simply part of the client. It could even live outside of the data centre housing the microservices. I also don't see how REST doesn't apply to both interfaces (client/BFF and BFF/microservice).
Yeah that's a good point. It's usually for when you have a team building microservices and a team making an angular app, for example, and that dev team are more front-end types who don't like having to work against a bunch of little purist services. Though I don't see any reason you can't use the same pattern for abstracting microservices and aggregation into a more usable facade for your customers, such that the microservices can be changed without impacting the facade.
An API endpoint should model the needs of the domain and business. Code to solve those problems and avoid over-engineering to adhere to strict and many time's inflexible specs. REST is nothing but guidelines anyway.
L
Lam Duc Trung

You can POST a deleted resource :). The URL will be POST /deleted-records and the body will be {"ids": [1, 2, 3]}


What are you trying to say?