I'm trying to create a REST API that allows the user to:
- create a resource if it doesn't exist.
- update a resource if it exists.
with the same request.
Given an example: I have a company that has many employees. I know that the way that usually an employee is inserted is using something like do a post request to /employees/
URL with a body that represents the employee to be created and as result, I will get similar content with a field that identifies the created resource. In most cases, if I try to insert another employee with data that conflicts with the first created employee, like the fiscal document code, I will get a 400 or 409 response that indicates a conflict with an existent resource. But if I want to create a permissive API that instead updates the present resource according to an attribute that I know that uniquely identifies the resource, what is the best approach?
A
POST
request means that the request is not idempotent, but an "upsert" request is idempotent. So I think that design the API using thePOST
method is not adequated.A
PUT
request is idempotent but refers to a specific resource or a set of resources. Semantically, do aPUT /employees/
means that all existing employees are matched in this request.Use something like
PUT /employees/123
, that 123 is an identifier of the employee is not possible when its identifier is synthetic and generated by the service. The user cannot know what identifier to use in this case.
Is possible to create a RESTful API that allows this kind of "upsert" request? If possible, how can I do?
1 Answer 1
There is no rule stating that the behaviour of your POST
handling may not be idempotent. It is just that all other methods are considered as "must be idempotent", but that doesn't mean POST
isn't allowed to be idempotent.
So you wouldn't violate any rules if you would implement POST
to also function as an upsert
operation. However, be very careful about this. What will you do if Alice her fiscal document is the same as Bobs, but her social security number is the same as Charlies? So you update Bob, Charlie, both, neither, or do you merge them somehow?
Looking at things from an API standpoint, the cleanest way to do this would probably be to have POST /employees/
response with 307 Temporary Redirect
+ Location: /employees/123
- that way the client can decide to overwrite whatever is there, or go a GET /employees/123
first so somebody (or maybe just some code) can compare the two to decide if overwriting that data is safe.
Another option all together, if you source system is 100% in charge of what employees exist, is to use some unique reference from the source system (say "xyz") and always PUT
employees (it doesn't have to be a PUT /employees/xyz
, it could be PUT /source-system-employees/xyz
to signify that the identifier used there is specific to the source system, letting you use /employees/123
in places that have nothing to do with that source system).
PUT /employees/{employeeUuid}
endpoint, generate the identifier on the client and create a resource if it does not yet exist, otherwise update it (with access checks, if necessary).