12

Currently I am developing an API and within that API I want the signed in users to be able to like/unlike or favorite/unfavorite two resources.

My "Like" model (it's a Ruby on Rails 3 application) is polymorphic and belongs to two different resources:

/api/v1/resource-a/:id/likes

and

/api/v1/resource-a/:resource_a_id/resource-b/:id/likes

The thing is: I am in doubt what way to choose to make my resources as RESTful as possible. I already tried the next two ways to implement like/unlike structure in my URL's:

Case A: (like/unlike being the member of the "resource")

PUT /api/v1/resource/:id/like maps to Api::V1::ResourceController#like
PUT /api/v1/resource/:id/unlike maps to Api::V1::ResourceController#unlike

and case B: ("likes" is a resource on it's own)

POST /api/v1/resource/:id/likes maps to Api::V1::LikesController#create
DELETE /api/v1/resource/:id/likes maps to Api::V1::LikesController#destroy

In both cases I already have a user session, so I don't have to mention the id of the corresponding "like"-record when deleting/"unliking".

I would like to know how you guys have implemented such cases!

Update April 15th, 2011: With "session" I mean HTTP Basic Authentication header being sent with each request and providing encrypted username:password combination.

Ivan
  • 874
  • 10
  • 32

3 Answers3

12

I think the fact that you're maintaining application state on the server (user session that contains the user id) is one of the problems here. It's making this a lot more difficult than it needs to be and it's breaking a REST's statelessness constraint.

In Case A, you've given URIs to operations, which again is not RESTful. URIs identify resources and state transitions should be performed using a uniform interface that is common to all resources. I think Case B is a lot better in this respect.

So, with these two things in mind, I'd propose something like:

PUT /api/v1/resource/:id/likes/:userid
DELETE /api/v1/resource/:id/likes/:userid

We also have the added benefit that a user can only register one 'Like' (they can repeat that 'Like' as many times as they like, and since the PUT is idempotent it has the same result no matter how many times it's performed). DELETE is also idempotent, so if an 'Unlike' operation is repeated many times for some reason then the system remains in a consistent state. Of course you can implement POST in this way, but if we use PUT and DELETE we can see that the rules associated with these verbs seem to fit our use-case really well.

I can also imagine another useful request:

GET /api/v1/resource/:id/likes/:userid

That would return details of a 'Like', such as the date it was made or the ordinal (i.e. 'This was the 50th like!').

joelittlejohn
  • 11,665
  • 2
  • 41
  • 54
  • Hi @joelittlejohn and @jim-ferrans, My bad! What I meant with "session" is actually HTTP Basic Authentication that sends the encrypted username:password combination with _each_ request and authenticates the user. So I always have a "current_user" while "liking" or "unliking" and only while action is being executed. Credentials or authentication are not saved on the server side. From my point of view it IS stateless. – Ivan Apr 15 '11 at 10:53
3

case B is better, and here have a good sample from GitHub API.

  1. Star a repo

    PUT /user/starred/:owner/:repo

  2. Unstar a repo

    DELETE /user/starred/:owner/:repo

Spark.Bao
  • 5,573
  • 2
  • 31
  • 36
0

You are in effect defining a "like" resource, a fact that a user resource likes some other resource in your system. So in REST, you'll need to pick a resource name scheme that uniquely identifies this fact. I'd suggest (using songs as the example):

/like/user/{user-id}/song/{song-id}

Then PUT establishes a liking, and DELETE removes it. GET of course finds out if someone likes a particular song. And you could define GET /like/user/{user-id} to see a list of the songs a particular user likes, and GET /like/song/{song-id} to see a list of the users who like a particular song.

If you assume the user name is established by the existing session, as @joelittlejohn points out, and is not part of the like resource name, then you're violating REST's statelessness constraint and you lose some very important advantages. For instance, a user can only get their own likes, not their friends' likes. Also, it breaks HTTP caching, because one user's likes are indistinguishable from another's.

Jim Ferrans
  • 30,582
  • 12
  • 56
  • 83
  • 1
    @jim-ferrans unfortunately I need the "/like" part of the url to be appended at the end of URL as I want to make a ruby gem to "like/unlike" or "favor/unfavor" things. I want to be able to put "acts_as_able" inside of a model. Then I want to append a corresponding noun to the url, for ex "/likes", and prepend it with a corresponding HTTP verb, for ex "DELETE". But this is kind of offtopic. The point is: I want the "/likes" part to be appended at the end of my resource. – Ivan Apr 15 '11 at 11:16