I like the Thierry Templier's approach, as described in the article Implementing Bulk Updates within RESTful Services. I'll just summarize the main ideas here, adapted to your context. Nevertheless, I do recommend reading the full article:
Request
Commonly, the POST
method is used to add a single element to the collection. So, we could have a resource that accepts both single element and collection of elements for its method. According to the input payload, the processing detects if a single or a bulk add must be done.
As a matter of fact, having another resource that uses an action name in resource path like /books/bulk
isn’t really RESTful, so it’s not the right approach.
When creating a single element, we can have something like this:
POST /books
Content-Type: application/json
{
"title": "The Lord of the Rings - The Fellowship of the Ring",
"author": "J. R. R. Tolkien",
(...)
}
And the following, in the case of a bulk operation:
POST /books
Content-Type: application/json
[
{
"title": "The Lord of the Rings - The Fellowship of the Ring",
"author": "J. R. R. Tolkien",
(...)
},
{
"title": "The Lord of the Rings - The Two Towers",
"author": "J. R. R. Tolkien",
(...)
},
(...)
]
Response
When creating a single element, the response is quite straightforward and commonly contains two things:
- A status code
201
(Created)
- An header
Location
contained the URL of the newly created element
201 Created
Location: http://example.com/api/book/{id}
The Location
header accepts one value and can be defined once within a response.
That said, since the semantics of a POST
method is up to the RESTful service designer, we can leverage the header Link
to provide this hint, as described below:
201 Created
Link: <http://example.com/api/book/{id}>, <http://example.com/api/book/{id}>
Transaction
In a bulk operation, you can consider a transactional approach:
- Everything works fine and all data is inserted
- At least one element has validation errors and nothing is added
- One or more insert fails and everything is rollbacked
422 Unprocessable Entity
Content-type: application/json
[
{
"index": 1,
"messages": [
{
"title": "The title should at least have three characters."
}
]
},
{
"index": 1,
"messages": [
{
"id": "The value of the field it isn't unique."
}
]
},
]
In the case of insertion errors:
500 Internal Server Error
Content-type: application/json
[
{
"index": 1,
"messages": [
"The book can't be added because of the error #22 (description)"
]
},
(...)
]
For non-transactional processing, the status code of the response will always be 200
and errors, if any, described in the response payload, as shown below:
200 OK
Content-type: application/json
[
{
"index": 1,
"status": "error",
"messages": [
"The book can't be added because of the error #22 (description)"
]
},
{
"index": 2,
"status": "success",
"auto-generated-id": "43"
},
(...)
]