Do I violate RESTfulness when using POST as UPDATE OR CREATE
Asked Answered
C

2

21

Given the requirement other departments have for our REST API they would like to use POST not just for CREATE but for UPDATE OR CREATE. I know in a RESTful API PUT could or should be used for that, but because clients have to update information that is used to build the URI, we cannot use that. It would change the URI and make PUT not idempotent anymore... (the old URI would not exist after the first PUT).
tl;dr we cannot use PUT

In the HTTP/1.1 specs POST is defined as

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI

but also

The action performed by the POST method might not result in a resource that can be identified by a URI.

To stay RESTful I would explain the update functionality as an Deletion of the old element and then a Creation of the new one, which would be an acceptable functionality for POST I would say.

We would return a #201 when creation was successful and a #200 when it was just an update.


In our API it is "possible" to address the right element without the URI (e.g. for updating it with POST), because all the URI building primary key parts are in the resource body, so the API knows which element the client wants to access.


Example

(This is just an example of the behavior for POST. Not for the data structure of the resource. Of course using PUT would be completely right for /cars/)

POST /cars/ HTTP/1.1
<car>
    <id>7</id>
    <status>broken</status>
</car>

response: #201
and then

POST /cars/ HTTP/1.1
<car>
    <id>7</id>
    <status>fine</status>
</car>

response: #200
now a GET on /cars/7 would return the following:

<car>
    <id>7</id>
    <status>fine</status>
</car>
Chromatograph answered 18/9, 2014 at 9:33 Comment(3)
I've just deleted my original comment after looking at your example as your transaction appears to be idempotent. You are setting the status of car 7 to be fine. If you were to rerun this method again and again then you would still end up with the same result. Therefore there's no good reason that this particular example couldn't be a PUT method.Sebiferous
This answer covers PUT v POST the best I feel. If your action is idempotent then use PUT, otherwise use POST.Sebiferous
@Sebiferous I think your first comment was just fine. The example was just for the behavior (added that to the question). Sorry for the vagueness! :/ I also saw the answer you are referencing, but sadly it does not really use good sources (the PDF is not really helpful). Btu to comment your last statement My POST (with Update or Create) is now kinda indempotent, because calling it again and again would result in the same data set. Creating this idempotence with PUT seems impossible for my solution (because the uri changes)Chromatograph
U
15

"Violating RESTfulness" is not strictly defined.

A good reference to degrees of REST-like behaviour in an interface is the Richardson Maturity Model which splits degrees of support for REST ideals into 4 tiers (with 0 being "not REST at all" and 3 being "completely REST, but hardly anyone does this yet")

Your choice breaks only slightly with RESTful HTTP-based design at level 2 within this model. However, it is the kind of compromise that many real-world projects have to contend with.

POST is open-ended enough that it can be idempotent if you wish, but HTTP does not require it to be. So you have not broken with HTTP, just missed the opportunity to use the more germane PUT method. The reverse situation, using PUT for a non-idempotent part of the API, would be more problematic.

As a personal opinion, I think that if you remain self-consistent when handling the different HTTP methods and routes, and document this change to expectations clearly, then you can reasonably call your API "RESTful". I think the response code split 200/201 is enough that a self-consistent client could be written. Although I would prefer to see both create and update as PUT here, it's not something that would cause me more than a moment's pause.

You might consider supporting update or create on the collection route (POST /cars in your example) as suggested, plus update on the item route (POST /cars/7). The concept of "update or create" is common enough in database and ORM frameworks, that it would be easily understood. A client could also use the latter update-only route in confidence that it would not accidentally auto-create a new record.

Urbanist answered 18/9, 2014 at 9:46 Comment(4)
I have read through your link and think its very useful for me to explain the deviation from the standard in the API dev documentation. Although I know its very hard to define "Restful", your thoughts and explanations cover my train of thoughts. Thank you very much for your answer! (I will wait if somebody other has something new to add and then mark you as the answer :))Chromatograph
"deviation from the standard" - REST is not a standard. Or if it is, what's its reference number? :) It's an architectural pattern. You choose how much you follow it. You can also choose your entity modelling. Following CRUD isn't mandatory.Gastroscope
Yes. But for best practice, seperate POST as create and PUT as update.Ringe
@hien711: That is not always the case. PUT makes a lot of sense for create, when the id is known and meaningful. Perhaps ask a separate question about that, if it is not clear.Urbanist
F
1

Naturally, it is not possible to ensure that the server does not generate side-effects as a result of performing a GET request; in fact, some dynamic resources consider that a feature. The important distinction here is that the user did not request the side-effects, so therefore cannot be held accountable for them.

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

So idempotence is about the server-side side-effects. Your second PUT does not have any other side-effects (because of 404), than the first one, so using PUT is idempotent here.

The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI

In this case you don't create a new resource, you just add a new resource identifier and remove an old resource identifier. So POST is inappropriate.

The action performed by the POST method might not result in a resource that can be identified by a URI.

This can mean anything you want... Ppl use POST for non-creational purposes when they are out of options, but you are certainly not.

In our API it is "possible" to address the right element without the URI (e.g. for updating it with POST), because all the URI building primary key parts are in the resource body, so the API knows which element the client wants to access.

You have to use IRIs (URL) to identify resources if you don't want to violate the uniform interface (identification of resources) constraint.

You have to use hyperlinks if you don't want to violate the uniform interface (hypermedia as the engine of application state) constraint. The IRI structure does not carry any semantics for the clients, so it can be anything if it is up to them. You have to use link relations or some attribute from a vocab to add semantics.

You have to use at least vendor specific MIME types if you don't want to violate the uniform interface (self-descriptive messages) constraint.

By definition a REST webservice meets with all of the REST constraints. The term RESTful was created when ppl - who had no idea about REST - started to call their web services as REST. So after that ppl created the RESTful term, which means that the so called REST service violates none of the REST constraints. After that the term RESTful was exhausted too. Nowadays we distinguish hypermedia API and web API. The hypermedia API means that the web service meets with the HATEOAS (hypermedia as the engine of application state) constraint. The web API can be anything, which claims to be REST.

Fineness answered 18/9, 2014 at 11:44 Comment(6)
If I accept PUT for updating these entitys, I have to accept requests where the URI is different from the body, which feels not good for me, but maybe thats unfounded. But how should my API react on the second PUT? A #404 would not be right, because its a legitimate call. The client wants to Update or Create a resource, the API can't deny that because there is no element with that URI. PUT calls have to create the resource, if its not there. BUT THEN... the URI becomes irrelevant for any PUT call because I just ignore it anyway and work with the body. Would this really be a hypermedia API !?Chromatograph
And thank you very much for your long and detailed answer! My understanding of idempotent was wrong. It would be very nice if you could include my concerns from the last comment into your answer.Chromatograph
"If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI." - You don't have to create the new resource with PUT if you don't want to... can create != must createFineness
If I accept PUT for updating these entitys, I have to accept requests where the URI is different from the body, which feels not good for me, but maybe thats unfounded. Can you give me an example?Fineness
Uhm, yeah. https://mcmap.net/q/661039/-restful-api-should-i-send-uri-parts-in-the-response-body-and-how-about-updating-them-closed we already discussed that some days ago... The question for me is just how the API should behave? Ignore the URI and just make an UPDATE/CREATE on the given resource from the body? Or just look if under the URI is a resource and try to update it? But what if under the given URI is no resource: create a new one with the primary key given in the body or with the primary key given in the URI? This feels very inconsistent for me... | The real entities we are working with are something like: /parameter/<id>/<validFrom> and we change the dateChromatograph
You never ignore the URL since that identifies the resource you want to work on. URLs can break in time, there is nothing wrong with that. You have to accept the fact that the client is not always in sync with the server. That's why you send your requests, to update the client or to update the server. Anything else depend on your needs. You can keep the old URL if you want and use the PUT on the same resource. You can remove the old URL if you want and send back a 404, since there is no resource assigned to the URL. etc...Fineness

© 2022 - 2024 — McMap. All rights reserved.