Sirish Renukumar
Sirish Renukumar

Reputation: 1717

Can a RESTful POST method be implemented to be idempotent?

I am designing a RESTful API that is managing favorites. Each favorite resource can contain details of two items that are considered as part of the favorite.

 HTTP POST /favorites

 {  "item1" : "ball",
    "item1-ID" : "1",
    "item2" : "bat",
    "item2-ID" : "2"
 }

Please excuse the rudimentary JSON payload. The focus is however on the semantics of the POST

The above POST method creates a new favorite resource (that contains a ball (ID 1) and a bat (ID 2))

My question is with regard to the expected behavior when the same POST request is sent twice. The first request will create a favorite (as expected). What should happen when the second request is sent?

1) Signal an error with 409

2) Signal a success with 201

1) is not idempotent (as POST is), while 2) makes POST idempotent.

Which is the correct approach?

Upvotes: 12

Views: 8297

Answers (5)

Phil
Phil

Reputation: 481

Duplicate idempotent POST requests should return the previously created resource in response if the status code of the response was 200. Idempotency Enforcement Scenarios

Upvotes: 0

Punit Mehta
Punit Mehta

Reputation: 11

In general, it depends on you, how you want to design the underlying system. In most cases, POST creates a new resource (Yes, there are instances when you use POST and not create anything new. For example, you have a use-case where in you want to search on a very long list of the parameters and GET might not be the way!. This is completely acceptable and not a violation of REST principles.)

Commenting on your specific case,

What should happen when the second request is sent?

  • You should ask the following questions to yourself.

    1. Is having a duplicate request (a request with the same payload/headers/time-stamp etc.) a "different" request for your system ? If YES - treat it normally as if it's not duplicate. I assume that the next systems to which the request is going are also understanding this as a new request, otherwise they could fail and flag error. For example, MySQL could throw Duplicate Primary Key error (Refer: Error Code: 1062. Duplicate entry 'PRIMARY')

    2. If the answer to (1) is NO - Is the duplicate occurrence of this request an expected behaviour of the clients ? (For example, you tend to get duplicate requests from mobile if it's intermittently getting the network.) If YES - return 200 OK. Note the idempotency here.

    3. If the answer to (2) is NO - throw 409 conflict with a custom error-code which client can handle and react accordingly. I would also investigate the unwanted behaviour of client on throwing the duplicate requests and fix it if that's doable.

Upvotes: 0

D.Shawley
D.Shawley

Reputation: 59563

You are thinking about this in the wrong way. If a POST creates a new resource then the resource should have a URL that identifies it (e.g., http://.../favorite/1). When the second POST with the same payload happens, is a new resource created? If so, then you have two separate resources with unique URLs. If you application does not create a new resource, then the second POST would return the same URL as the first one.

Edit

The POST section of RFC7231 does not prohibit it from being implemented in an idempotent manner. It does provide some guidance though:

  • If a POST creates a new resource, then it SHOULD send a 201 (Created) response containing a Location header identifying the created resource
  • If the result of a POST is equivalent to an existing resource, then the server MAY redirect the UA to the existing resource by returning a 303 (See Other) with the appropriate Location header

POST is not required to change the state of a resource. However, GET and HEAD are required to be idempotent. I'm not aware of any method that is required to change state on the server.

Personally, I implement resource creating POST methods as returning a 303 redirect to the canonical URL for the resource as a matter of practice. I wasn't aware of the RFC distinguishing status codes for resource creation and re-use responses. The benefit of this approach is that it removes the need to include the created resource in the POST response and the GET to the canonical URL will cache the response in any intermediate caches that may be present. It also allows for the implementation of intelligent clients that do not retrieve the response and use the canonical URL (from the Location header) instead.

In your case, I think that I would adopt the 303 approach provided that a favorite has a URL of it's own; 201 would be strange if it did not. If there is very good reason for distinguishing the creation and re-use cases in the response, then a 201 when the new resource is created and a 303 when it is reused is appropriate. What I would not do is to return a 409 unless your application requires you to deny the creation of a resource that already exists.

Upvotes: 5

jjmontes
jjmontes

Reputation: 26895

To me, as you pictured it, it should create a new favorite. But this feels strange for almost any application. What if:

  • your favorites could be defined as non-repeatable if they are exact copies
  • you could send an ID with the favorite (not a good idea, as it'd be client-based), and insert or update based on that
  • you could send some other unique field and use that to define whether it is a "create" or an "update" (ie. "favorite name" or "position in menu")

As you see, it all depends on your application.

Maybe you can get some ideas from this article I happened to write some time ago: A look into various REST APIs . Don't miss the summary at the bottom.

Upvotes: 1

Albert Pinto
Albert Pinto

Reputation: 402

What should happen when the second request is sent?

It all depends on your implementation . Say if you have some logic in your controller that checks if ID 1 and ID 2 exist in the data base then update the value else create the new record. The whole thing is not depended on POST request.

AND FYI HTTP response code for success is 200 and not 201.

Your current approach is not correct and the question is not a JAVA question

Upvotes: 0

Related Questions