Hacker News new | ask | show | jobs
by mamoriamohit 1396 days ago
This is good for fetching a single item. What if you want to list resources?

Eg. List all comments of a blog?

It has to be GET /blog/:blogid/comments

Then, how about creating a new comment?

It'll be POST /blog/:blogid/comments

When both of these have the blog's hierarchy in the URL, should we take it away when fetching a single item? Or the stay consistent, we should simply do:

/blog/:blogid/comments/:commentid

It's hard.

3 comments

> This is good for fetching a single item. What if you want to list resources?

You're just asking for one of the most basic features in resource-oriented architectures: query a collection resource with a filter predicate and possibly pagination.

GET /comments?blog=<blogId>

> Then, how about creating a new comment?

Same thing: just POST it to /comments with a relevant request document, such as

{"blog":<blogId>,"comment":<blah blah blah>}

Your POST request then receives a response with a link to the newly-created comment resource, and that's it.

> When both of these have the blog's hierarchy in the URL, should we take it away when fetching a single item?

Resources do not have a hierarchy per se. You've just been passing request parameters through the path. That's it.

> It's hard.

Not really. It's a matter of understanding that resource-based architectures just deal with resources, and not resource hierarchies which don't really exist per se.

What you mean by hierarchy is actually just passing parameters through the path, but those parameters can be passed elsewhere.

In this case, I think of this as more of a search endpoint than a list. What would you think about using a parameter to specify the relation? i.e.

GET /comments?blogId=<id>

List all comments of a blog: GET /blog_comments/:blogId

Creating a new comment: POST /comments (blog_id, author_id, category_id all in the post body)

Single comment: GET /comments/1234

You can pretty much follow all the same normalization rules you would for a SQL database. /blog_comments is a kind of query in a virtual N-to-N resource that maps blogs to comments, get it?

Consider for example, "all posts that this particular author commented on". That would be insane to put into a hierarchy URL. For a flat one, you can just:

GET /commented_by/:authorId

Boom, done. Sometimes, you'll need more than one id, of course, but that does not imply "nesting". Consider "all posts where these two authors interact on comments":

GET /discussions_involving/:authorId1/:authorId2

There is a cap in what you can do with a hierachy, and the web is not a filesystem with folders, it's a graph with unlimited possibilities.

Are request parameters not a preferred choice? Especially if filtering for multiple authors.

GET /discussions?author=:authorId1,:authorId2

If both authors had participated.

Or if only one or the other had participated:

GET /discussions?author=:authorId1&author=:authorId2

Yes! Query string parameters are great for this kind of stuff.

I guess the choice on the URL format depends on how you're going to route that on the server side, so there are multiple ways of doing it.

Depending on the nature of the resource, you might have to be careful with URI canonicalization.

Consider for example the "diff between account balances" endpoint:

GET /account_balance_diff?accid=1&accid=2

Should the diff be presented as from 1 to 2, or from 2 to 1? Query string parameters don't have a particular order to them, and when canonicalizing, some user agent might decide to reorder these parameters.

If you do:

GET /account_balance_diff/1/2

Then, there are two distinct URIs (one for diff 1->2 and other 2->1) and no ambiguity on meaning.

You could also use some kind of index on the parameters to preserve order:

GET /account_balance_diff?acc[1]=123&acc[2]=456

Your other example using a comma should also be fine:

GET /account_balance_diff?accs=1,2

Let's go back to the "discussions" example. What should happen if I GET /discussions without any author id? Our inner guts tell us that there should be something there (after all, I'm filtering _something_), but REST implies absolutely nothing about this relation. To REST and HTTP, you could have a /discussions URL that is completely unrelated to /discussions?filter.

Having a concise, clear URL forming pattern is great. It's not REST though, it's a separate thing, incredibly relevant to us humans, but irrelevant to the architectural style.

A similar confusion happens with error codes. I've seen a lot of people answer 406 Not Acceptable as a status for lock errors and invalid requests. It sounds nice, but it's not designed for that. 406 means the server can't deliver that media type (you asked for PNG but I only have JPG, for example). That 406 is part of the content negotiation mechanism of HTTP, not a lego block to reuse as application logic. The URI is the same, it's role is to be a primary key for the web, not to express hierarchy.

(btw sorry for the long post, I ended up venting a lot and got into several tangents completely unrelated to your comment)