Hi I'm looking for best practices with API design to batch update a collection with an API call.
My collection has URL /api/v1/cars and I would like to update all the cars in the collection to add a timestamp of the current time.
{
data: [
{
manufacturer: 'Porsche',
timestamp: ...
},
{
manufacturer: 'BMW',
timestamp: ...
}
{
manufacturer: 'Peugeot',
timestamp: ...
}
}
I thought about a few options but I can't figure what is the best practice.
Should it be:
1/ Modelled as another resource such as POST api/v1/cars/updateTimestamp
2/ Passed as a query parameter: PUT api/v1/cars?updateTimestamp
3/ Pass in the body of the request:
POST api/v1/cars
{"operation":"Update timestamps"}
I'd like to emphasize that the whole processing should be done on the back-end and not passed by the client. Same problem would happen for any complex processing that happens on the back end.. How could I keep the API RESTy in this case.
Thanks a lot for your help/any pointer to relevant ressources.
As there is no partial PUT
defined in HTTP, you either need to send the whole entity for each resource to update or use some other operations.
As POST
is an all-purpose operation you can use it to create a short-living temporary resource at the server (which further does not even have to have an own URL). On receipt the server can update either all specified entries or all entries in general with some provided field-value combination (certain table alterering may be necessary though if the column is not yet know in relational databases)
A simple request may look like this:
POST /api/v1/cars/addAttributes HTTP/1.1
Host: example.org
Content-Length: 24
Content-Type: application/json
If-Match: "abc123"
{
"timestamp": "..."
}
This approach has the advantage that it could be sent to the server anytime, even without prior knowledge of the current state. This, however, also has the danger that certain entries get updated which shouldn't be affected. This can either be influenced by specifying an If-Match
header, which points to a certain version-hash and is changed on every entity update, or by adding a certain restrictor to the JSON body which the server also has to understand.
Similar to POST
, which can literally send anything to the server, PATCH
is intended for modification of resources. A single request explicitely may update multiple resources at once, however, the client needs to define the necessary steps to transform the resources from state A to state B. Therefore, the client also needs to have the latest state of the resource in order to successfully transform the resource to its newest state (-> ETag and If-Modified HTTP headers)
A JSON Patch request for the provided example may therefore look like this:
PATCH /api/v1/cars HTTP/1.1
Host: example.org
Content-Length: 215
Content-Type: application/json-patch+json
If-Match: "abc123"
[
{ "op": "add", "path": "/data/1", "value": [ "timestamp", "..." ] },
{ "op": "add", "path": "/data/2", "value": [ "timestamp", "..." ] },
{ "op": "add", "path": "/data/3", "value": [ "timestamp", "..." ] }
]
Where /data/1
, /data/2
and /data/3
are the unique identifiers for the resources of Porsche
, BMW
and Peugeot
.
PATCH
specifies that a request has to be atomic which means that either all or none of the instructions succeed which also brings some transaction requirements to the table.
As already mentioned, in POST
requests you can literally send anything you want to the server at least as long as the server is capable of understanding what you are sending. It is therefore up to you how you design the request structure (and the server logic as well).
PATCH
on the other hand, especially with JSON Patch
defines some strict rules and predefined operations typically to traditional patching. The transaction requirement can be a burden but also a benefit. In addition to that, PATCH
is not yet final and still in RFC though it is already widely available.