(Pardon the question title; hard to summarize this question.)
On Facebook, you like
things. On Twitter, you follow
people. On GitHub, you also follow
people and star
repos and gists.
All of these cases are pretty similar: those connections are lightweight, not really "resources" themselves. E.g. none of those three APIs expose public IDs for such connections.
This raises the question: what's the "best" (in terms of REST) way to expose an API for creating/querying/deleting those connections?
Facebook does [1]:
GET /:id/likes
to query an object's likes (more precisely, the users that like that object)POST /:id/likes
to like something (on behalf of the auth'ed user; no req body needed)DELETE /:id/likes
to unlike something (on behalf of the auth'ed user)
The querying and creating make sense, but the DELETE
is a bit "unRESTful", because you're not actually deleting the /:id/likes
resource (an array of users that like that object).
This discrepancy shows itself in another case [2]:
GET /me/likes/:id
to query whether you like something
So querying your connection is querying a totally different resource than creating or deleting it.
GitHub leans to the /me/likes/:id
style for following users and starring repos [3]:
(Note that GitHub's /user
represents the auth'ed user, like Facebook's /me
.)
GET /user/starred/:owner/:repo
for querying whether you have a repo starred (returns 204 or 404, no body either way)PUT /user/starred/:owner/:repo
for starring a repo (no body needed in request)DELETE /user/starred/:owner/:repo
for unstarring a repo
This is much more consistent, but unfortunately this separates individual "stars" from the group:
GET /repos/:owner/:repo/stargazers
to query the users who've starred a repo
GitHub, interestingly, uses a different style for starring gists [4]:
GET /gists/:id/star
for querying whether you have a gist starredPUT /gists/:id/star
for starring a gistDELETE /gists/:id/star
for unstarring a gist
This keeps the starring action with the gist resource, like Facebook, rather than the user resource.
GitHub doesn't publicly expose gists' stargazers, but presumably it'd be e.g.:
GET /gists/:id/stargazers
to query the users who've starred a gist
While "stargazers" is indeed a different resource/name than "star", the names are similar and clearly related, and they're both on the same resource.
The only downside I can think of to this is naming the resource. Something like star
works, but actions like follow
or like
are trickier.
(Not bothering to include the Twitter API as an example since it's hardly RESTful.)
There's clearly no perfectly RESTful API for creating/querying/deleting things that aren't proper resources, but are there other pros/cons I'm not seeing, or other styles to consider?
Thanks!
I think
DELETE /:id/likes to unlike something (on behalf of the auth'ed user)
makes sense, since the like would have a compound key of the liked object's id and your user id, so specifying an id is just redundant when your authenticated login already tells who you are, and you don't even have permission to delete other user's likes anyway.
Explicitly specifying this (as suggested by Aseem Kishore) like
DELETE /:id/likes/me
...might be a bit more clear.
One thing I liked about the
/me/likes/:id
style is that the likes do feel like individual, addressable resources -- e.g. they have individual IDs (that happen to be the same as the things I'm liking).GitHub's repo API uses this nicely for creating/querying/deleting "star" connections to repos, but there's a discrepancy for fetching all "star" connections for a given repo.
Perhaps the discrepancy could be addressed by changing the way you address these connections: instead of relying solely on the object ID, use the (auth'ed) user's ID too. E.g.:
GET /:owner/:repo/stargazers
to query all the users who've starred this repoGET /:owner/:repo/stargazers/:id
to query whether user:id
has the repo starred -- this can be the auth'ed user by specifyingme
!PUT /:owner/:repo/stargazers/me
to star a repo -- this'll only work for the auth'ed userDELETE /:owner/:repo/stargazers/me
to unstar a repo -- dittoNow all the resources/actions are together, the actions are consistent, and naming is easy.
Edit: Another benefit of this approach is that you can easily and efficiently query whether other users like/follow/star an object, as well.
Edit: But a downside is that the resources aren't technically correct anymore --
GET .../stargazers
returns a list of users, butGET .../stargazers/:id
returns a connection, not a user. Oh well?[Edited again to support passing
me
as the:id
here too.]