It is a fairly common requirement to support undeletes or delayed/batched deletions for data services. What I\'m wondering is how to implement this in a RESTful way. I\'m torn b
I'm also running in this problem and I've been looking on the Internet for what feels like the best solution. Since none of the main answers I can find seem correct to me, here is my own research results.
Others are right that the DELETE
is the way to go. You could include a flag to determine whether it's immediately a permanent DELETE
or a move to the trashcan (and probably only administrators can do an immediate permanent DELETE
.)
DELETE /api/1/book/33
DELETE /api/1/book/33?permanent
The backend can then mark the book as deleted. Assuming you have an SQL database, it could be something such as:
UPDATE books SET status = 'deleted' WHERE book_id = 33;
As mentioned by others, once the DELETE
is done, a GET
of the collection does not return that item. In terms of SQL, this means you must make sure not to return an item with a status of deleted
.
SELECT * FROM books WHERE status <> 'deleted';
Also, when you do a GET /api/1/book/33
, you must return a 404 or 410. One problem with 410 is that it means Gone Forever (at least that's my understanding of that error code,) so I would return 404 as long as the item exists but is marked as 'deleted'
and 410 once it was permanently removed.
Now to undelete, the correct way is to PATCH
. Contrary to a PUT
which is used to update an item, the PATCH
is expected to be an operation on an item. From what I can see, the operation is expected to be in the payload. For that to work, the resource needs to be accessible in some way. As someone else suggested, you can provide a trashcan
area where the book would appear once deleted. Something like this would work to list books that were put in the trashcan:
GET /api/1/trashcan/books
[{"path":"/api/1/trashcan/books/33"}]
So, the resulting list would now include book number 33, which you can then PATCH
with an operation such as:
PATCH /api/1/trashcan/books/33
{
"operation": "undelete"
}
If you'd like to make the operation more versatile, you could use something such as:
PATCH /api/1/trashcan/books/33
{
"operation": "move",
"new-path": "/api/1/books/33"
}
Then the "move" could be used for other changes of URL wherever possible in your interface. (I am working on a CMS where the path to a page is in one table called tree
, and each page is in another table called page
and has an identifier. I can change the path of a page by moving it between paths in my tree
table! This is where a PATCH
is very useful.)
Unfortunately, the RFCs do not clearly define the PATCH
, only that it is to be used with an operation as shown above, opposed to a PUT
which accepts a payload representing a new version, possibly partial, of the targeted item:
PUT /api/1/books/33
{
"title": "New Title Here"
}
Whereas the corresponding PATCH
(if you were to support both) would be:
PATCH /api/1/books/33
{
"operation": "replace",
"field": "title",
"value": "New Title Here"
}
I think that supporting that many PATCH
operations would be crazy. But I think that a few good examples give a better idea of why PATCH
is the correct solution.
You can think of it as: using patch is to change a virtual field or run a complex operation such as a move which would otherwise require a GET
, POST
, DELETE
(and that's assuming the DELETE
is immediate and you could get errors and end up with a partial move...) In a way, the PATCH
is similar to having any number of methods. An UNDELETE
or MOVE
method would work in a similar way, but the RFC clearly says there is a set of standardized methods and you should certainly stick to them and the PATCH
gives you plenty of room to not have to add your own methods. Although I did not see anything in the specs saying you should not add your own methods. If you do, though, make sure to clearly document them.