Search code examples
apirestapi-design

Best practice to batch update a collection in a REST Api call


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.


Solution

  • 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.

    POST

    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.

    PATCH

    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.

    Discussion

    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.