What to do when REST POST provides an ID?
Asked Answered
T

3

16

I'm developing a JAX-RS API that includes a simple "Person" table with fields "id" and "name", where the "id" is tied to an autonumber in a mysql database. A typical use case would be to POST a new person.

A POST of a JSON message {"name":"Bob"} might return, for example, {"id":101,"name":"Bob"}.

What if the caller requests a POST of an object that includes an identifier? It seems my options are to:

  • Reject the request as invalid
  • Delete the id from the request and continue to process
  • Treat the POST like an UPSERT (on update failure, delete the ID and insert)
  • Attempt to create the new record using the provided id

The last option seems dodgy from a security perspective. If I'm using mysql, a malicious user could ramp my autonumber up to a max value in one request.

How should the inclusion of an id in a POST request be handled in a REST API?

Toxophilite answered 31/10, 2015 at 14:36 Comment(4)
Either option #1 or option #2. Option #2 might be more convenient, but possibly confusing if somebody POSTS something with an id later and then doesn't understand why you didn't use it.Ferrell
I am in favor of rejection. If a user sends invalid request you can never be sure what the actual intent was or the exact nature of his error.Coolth
Reject it. 400 Bad Request. The whole point of server-created identifier is for .. well, the server to create it. This is the general nature of POST. Allowing the client to interfere with this process, may have undesired consequences. I's stay away from 2 also for the reason Bill mentioned, and PM implied. Send a 400 with error messageSedgewick
Yeah, rejecting an ID seems the way to go. I've also got read-only create and update date fields, which I'm more inclined to ignore than reject if provided.Toxophilite
H
6

You should definitely reject all the requests that are hitting /users/ endpoint. First of all for security reasons (at DB level), secondly this is not the client's job to generate/suggest the IDs.

So the answer is to reject the request as invalid along with appropriate status code (400) and a message explaining the reason of rejection.

The second option is unintuitive, one that is sending and ID (which as I as wrote already is a bad idea) - would not expect to receive different ID that it posted. Sending ID in a body, makes sense for PUT request and it assumes that the object is already created/existing - this is an update.

The third option will not be RESTful - there's no upsert in REST - POST creates new resources. The fourth option doesn't make sense at all - this is not client's job to provide IDs.

Handicap answered 2/11, 2015 at 8:13 Comment(3)
I believe the UPSERT statement could map nicely to the PUT method according to tools.ietf.org/html/rfc7231#section-4.3.4Insobriety
PUT with an ID is a natural application of the PUT verb with the expected result that it will either replace the resource or create it. The client having no business suggesting an ID is an opinion, and a shaky one at best. PKs are generated by the database and should be constrained to the database implementation and NEVER used by the client. A good domain will assign unique IDs in application code that will be its business ID for that domain. That said, if you have a thin service layer and the domain is only using your rest API as a DAL, then passing the ID in a put is normal and not uncommon.Toulouse
I also acknowledge that many systems out there use the PK as the ID, I'm just saying that is one of the worst things you can do while designing a business system because you've now leaked your database implementation into the wild.Toulouse
T
1

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.

Toulouse answered 8/9, 2021 at 22:33 Comment(0)
S
0

One other option is just to ignore it with the @JSonIgnore annotation. on a GET, PUT, or DELETE you'd have /user/{id}, so the client should know the ID already. on a POST, the client should not be sending an ID, you're supposed to return the ID, or rather, an URL which you can GET the created object, which includes the ID.

Sailesh answered 4/11, 2015 at 4:44 Comment(1)
That would be equivalent to the second option.Toxophilite

© 2022 - 2024 — McMap. All rights reserved.