20

Designing an API, we've come up against the question of whether a PUT payload should contain the ID of the resource being updated.

This is what we currently have:

PUT /users/123 Payload: {name: "Adrian"}

Our route code extracts the ID from the URI and continues on with the update.

The first users of our API are questioning why we don't allow ID in the payload:

PUT /users/123 Payload: {id: 123, name: "Adrian"}

The reason we didn't allow it is because the ID is duplicated, in the payload and URI.

Thinking about this some more, we are coupling the resource to the URI.

If the URI doesn't have the ID, the payload will need to be amended:

PUT /no/id/here Payload: {name: "Adrian"} < What user???

Are there any reasons not to?

2 Answers 2

18

You are supposed to couple the Uniform Resource Identifier to the resource.

When REST is implemented with HTTP, you use GET to retrieve the current value of the resource and PUT to set a new value. The GET does not have a payload, so the resource has to be identified by the URI. And the PUT is logically done to the same URI and the payload should look exactly as what you want the next GET to return.

You can use POST to different URI, but it would only make less sense as it would be unnecessarily asymmetrical to the GET. POST to common URI could only make sense for creating new resources (POST /users/new, payload: {name: "Adrian"}, response {id: 345, name: "Adrian"}), but that's not idempotent and therefore should be avoided if you are striving for REST¹. Instead you should reserve ID with one call and then use PUT to set the new ID; that is fault-tolerant, because if the first request fails, the ID reservation can time out eventually and the PUT is idempotent. Or use client-generated UUID.


¹ The definition of REST does not say anything about idempotence, so I can't really claim it is not REST if you have non-idempotent operations. That does not change the fact that sticking to idempotent requests makes things more reliable without complicating them and is therefore recommended.

6
  • 8
    As far as I know a POST request does not have to be idempotent. So I see no problem with posting to /users (no need to add 'new').
    – lex82
    Apr 22, 2017 at 7:30
  • thanks for your reply. I see that it is a nice feature to have idempotency for all requests but who says this is required for a REST API? Certainly many APIs that call themselves RESTful allow non-idempotent POST requests (especially when the server generates ids for new resources). But even if you apply a very strict definition of REST, I don't see which architectural constraint ist violated with non idempotent POSTs like the example above.
    – lex82
    Apr 22, 2017 at 11:50
  • I can certainly image POST requests that violate a constraint. I just don't see why posting a resource into a collection and letting the server decide on its id is a violation of REST constraints.
    – lex82
    Apr 22, 2017 at 11:53
  • Let us continue this discussion in chat.
    – lex82
    Apr 22, 2017 at 12:46
  • 7
    The whole idea of POST is not to be idempotent....
    – EralpB
    May 19, 2017 at 8:34
6

Thinking about this some more, we are coupling the resource to the URI.

If the URI doesn't have the ID, the payload will need to be amended:

PUT /no/id/here Payload: {name: "Adrian"} < What user???

Are there any reasons not to?

The answer to this question depends on whether you want to allow the client to change the ID?

If the client can change the ID, via a PUT, then the URI for the resource will change, and you should provide a 301 Moved Permanently any time a resource accesses the old URI.

So for example you start with a resource at

/users/123

and the client PUTs the following onto the resource

{id: 222, name: "Adrian"}

the resource has been updated and its URI is now

/users/222

The Location field in the PUT response should contain the new URI, and if you go to /users/123 you should get a 301 response with the Location field pointing to the new /users/222 resource.

In most cases though you don't actually want the client to be able to change the ID, as this can get pretty messy pretty quickly. In that case the ID is something only the server can change, and you should leave it out of the PUT body, as the client cannot update this state.

If you PUT a require to a different URI on the same resource, say

/users/adian_lync

then if that resource does not exist the server should create it and create and ID when it is doing it

4
  • 1
    The reason for us questioning the placement of the ID in the payload was due to Backbone.js passing the ID in a PUT request by default. We can stop it from happening, but now I want to know why that's the default behaviour. Nov 28, 2014 at 17:42
  • 1
    Afraid I'm not familiar with Backbone.js. Seem unnecessary if the ID is included in the URL as well. Perhaps an oversight on the part of the devs Nov 28, 2014 at 17:54
  • I disagree that PUT /users/123 would remap to /users/222. The resource identity is permanent. Next call to /users/123 should return the record that was updated to 222, to remain RESTful.
    – CompEng88
    Jul 25, 2023 at 22:21
  • @CompEng88 REST is stateless from the client's perspective, nothing is assumed to be permanent including URLs, and there is a whole mechanism in HTTP (which is RESTful) for moving resources and updating clients as to the new location. That is what the Location header is about and the 301 and 308 status codes are for. See rfc-editor.org/rfc/rfc9110#status.301 Nov 29, 2023 at 12:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.