29

I have a resource that has a counter. For the sake of example, let's call the resource profile, and the counter is the number of views for that profile.

Per the REST wiki, PUT requests should be used for resource creation or modification, and should be idempotent. That combination is fine if I'm updating, say, the profile's name, because I can issue a PUT request which sets the name to something 1000 times and the result does not change.

For these standard PUT requests, I have browsers do something like:

PUT /profiles/123?property=value&property2=value2

For incrementing a counter, one calls the url like so:

PUT /profiles/123/?counter=views

Each call will result in the counter being incremented. Technically it's an update operation but it violates idempotency.

I'm looking for guidance/best practice. Are you just doing this as a POST?

Idan Gazit
  • 2,500
  • 3
  • 20
  • 24

4 Answers4

15

I think the right answer is to use PATCH. I didn't see anyone else recommending it should be used to atomically increment a counter, but I believe RFC 2068 says it all very well:

The PATCH method is similar to PUT except that the entity contains a list of differences between the original version of the resource identified by the Request-URI and the desired content of the resource after the PATCH action has been applied. The list of differences is in a format defined by the media type of the entity (e.g., "application/diff") and MUST include sufficient information to allow the server to recreate the changes necessary to convert the original version of the resource to the desired version.

So, to update profile 123's view count, I would:

PATCH /profiles/123 HTTP/1.1
Host: www.example.com
Content-Type: application/x-counters

views + 1

Where the x-counters media type (which I just made up) is made of multiple lines of field operator scalar tuples. views = 500 or views - 1 or views + 3 are all valid syntactically (but may be forbidden semantically).

I can understand some frowning-upon making up yet another media type, but I humbly suggest it's more correct than the POST / PUT alternative. Making up a resource for a field, complete with its own URI and especially its own details (which I don't really keep, all I have is an integer) sounds wrong and cumbersome to me. What if I have 23 different counters to maintain?

Community
  • 1
  • 1
Yaniv Aknin
  • 4,103
  • 3
  • 23
  • 29
  • 1
    Deviates from the standard a wee bit because does not have "the desired content of the resource after the PATCH action has been applied". E.g. the entity is a instruction to be executed, not the desired outcome. – Pocketsand Oct 15 '16 at 14:47
  • Following on from what @Pocketsand has said, does this approach not violate the "Manipulation of Resources Through Representations" sub-constraint under the "Uniform Interface" constraint? Where you should send back a representation of the resource you want to see, instead of sending instructions on how to manipulate it. – dayuloli Jun 30 '17 at 13:09
  • @dayuloli If you're still contemplating a solution I have [added an answer](https://stackoverflow.com/questions/1426845/incrementing-resource-counter-in-a-restful-way-put-vs-post/44852115#44852115) with what I decided to go with, it may or may suit your needs. – Pocketsand Jun 30 '17 at 17:42
  • You could use JSON instead of a new content type. Ex: {"$inc": "view": 1} – Alan Evangelista Jul 29 '21 at 13:22
  • While it might be the syntactical right choice in practice I would properly go with PUT or POST because PATCH is so rarely used it might cause issues with software that only expects GET, PUT, POST and DELETE. – Gellweiler Nov 06 '21 at 09:26
10

An alternative might be to add another resource to the system to track the viewings of a profile. You might call it "Viewing".

To see all Viewings of a profile:

GET /profiles/123/viewings

To add a viewing to a profile:

POST /profiles/123/viewings #here, you'd submit the details using a custom media type in the request body.

To update an existing Viewing:

PUT /viewings/815 # submit revised attributes of the Viewing in the request body using the custom media type you created.

To drill down into the details of a viewing:

GET /viewings/815

To delete a Viewing:

DELETE /viewings/815

Also, because you're asking for best-practice, be sure your RESTful system is hypertext-driven.

For the most part, there's nothing wrong with using query parameters in URIs - just don't give your clients the idea that they can manipulate them.

Instead, create a media type that embodies the concepts the parameters are trying to model. Give this media type a concise, unambiguous, and descriptive name. Then document this media type. The real problem of exposing query parameters in REST is that the practice often leads out-of-band communication, and therefore increased coupling between client and server.

Then give your system a uniform interface. For example, adding a new resource is always a POST. Updating a resource is always a PUT. Deleting is DELETE, and getiing is GET.

The hardest part about REST is understanding how media types figure into system design (it's also the part that Fielding left out of his dissertation because he ran out of time). If you want a specific example of a hypertext-driven system that uses and doucuments media types, see the Sun Cloud API.

Rich Apodaca
  • 28,316
  • 16
  • 103
  • 129
0

I think both approaches of Yanic and Rich are interresting. A PATCH does not need to be safe or indempotent but can be in order to be more robust against concurrency. Rich's solution is certainly easier to use in a "standard" REST API.

See RFC5789:

PATCH is neither safe nor idempotent as defined by [RFC2616], Section 9.1.

A PATCH request can be issued in such a way as to be idempotent, which also helps prevent bad outcomes from collisions between two PATCH requests on the same resource in a similar time frame. Collisions from multiple PATCH requests may be more dangerous than PUT collisions because some patch formats need to operate from a known base-point or else they will corrupt the resource.

Community
  • 1
  • 1
Gnucki
  • 5,043
  • 2
  • 29
  • 44
0

After evaluating the previous answers I decided PATCH was inappropriate and, for my purposes, fiddling around with Content-Type for a trivial task was a violation of the KISS principle. I only needed to increment n+1 so I just did this:

PUT /profiles/123$views
++

Where ++ is the message body and is interpreted by the controller as an instruction to increment the resource by one.

I chose $ to deliminate the field/property of the resource as it is a legal sub-delimiter and, for my purposes, seemed more intuitive than / which, in my opinion, has the vibe of traversability.

Community
  • 1
  • 1
Pocketsand
  • 1,261
  • 10
  • 15