What it really comes down to is what you want your API to support.
Allowing the create but also the replace
Sending a request to PUT /users/101
means that you're allowing the client to replace or create a resource with the ID 101 using the provided payload. A normal response to this is to, based on the response from the database, return 200 if it resulted in a replace or 201 if it resulted in an insert. Please note that this is a replace type update, not a partial update. If you need to only update pieces of the resource, the you're looking for PATCH which is kind of hard to implement in RDBMS.
If you don't want the behavior, then don't allow the verb
If you don't want to support this behavior then the best thing to do is simply not allow the PUT verb for that route at all. This is the RESTful thing to do. If you had implemented HATEOS (what actually makes something RESTful), then the PUT verb would simply not show up in the hypermedia links in the response payload and would return HTTP 405 Method Not Allowed if you attempted to use it anyway.
Are you actually RESTful?
You could, as mentioned, not allow the ID and require it be in the body instead, but it's not necessary and would actually not be very RESTful. What I mean is -- look at it through the lens of HATEOAS. It's easy to argue that if the client needs to know that the ID is supposed to be in the body on a PUT (possibly even the name of the ID, like if you called it "UserID", for example), then that basically ignores the whole goal of hypermedia where the client doesn't really need to know anything about the API to navigate it. That said, basically no one implements HATEOAS/Hypermedia but when trying to make an argument for what is and what is not RESTful, that is your guiding light IMO. In a truly RESTful API, the client only needs to know the resource paths and the shape of the entity being manipulated.
The thing with client-provided resource IDs, is that people try to make the argument that they couldn't possibly be able to know the next number in the integer identity. To that I say -- well.. that's your problems. For the love of god stop using PKs as your public IDs. Entity IDs should be globally unique, so let the client generate a GUID to use, for example, or do it on your server side for POSTs. Use that as your "Application Entity ID". That should be the ID you pass around between applications. You can argue about performance all day, but a) There is virtually no noticeable impact in databases like MongoDB and barely any noticeable impact in RDBMS and b) You don't link tables together with the application ID -- it's a where clause. You still use the database IDs/PKs on the database side for making joins, you just don't leak it out into the wild. But anyway...
When you want to create a new resource and not allow the ID be set by the client
So if your intent is to allow the client to create a resource, then use POST. For this, the ID should not be provided in the route (e.g. POST /users/101
) as that would not make a lot of sense. If you need to allow them to set the ID, then implement PUT as described above. The correct response for the POST is a 201 Created
if it was successful, and include a Location
header for the client to perform a GET to retrieve their new resource. In fact, there's a pattern for this called "PRG" (Post-Redirect-Get) where you return a 303 See Other
that will smooth that out for you. This works best in a web/front-end context but can work with Client-API too if the client will auto-follow redirects. Alternatively, you can simply return 201 with the new resource in the body and the new ID populated there.