Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Just do:

- /organizations/:id

- /blogs/:id

- /sections/:id

- /threads/:id

- /comments/:id

Why?

What determines how resources are related are links, not patterns in the URL. It's a graph, the URLs are just nodes, the links are what connect them.

If you _want_ to have some sort of hierarchy in the URL, you can redirect:

- /organizations/123/blogs/1234 -> /blogs/1234

Then each resource expresses how it is related to others using links (hrefs, or other mechanism for orther media types).

URIs can be anything like /ajndkandkjnasd, totally unreadable for humans. If it is a resource that contains links that can be followed, then it is a system that answers to a uniform interface, and that is the part of the REST dissertation that really matters (the other stuff are just implications of having such uniform interface).

For machine APIs, payloads could use "comment_id", "thread_id" and so on referring to a single resource. If any API users need to build URLs, they would do so by using a single property of the data (which is good enough as a link for me nowadays, for private APIs at least).



I'd do it this way as well for most cases.

Complicated URLs are hard to remember and annoying to share. The one benefit of the nested URL is that you get is of being able to "inspect" what organization or section or comment a piece of content belongs to. This is probably a rare edge case and something you should only implement if it makes your system much more usable, for some reason.

e.g. I'd probably rather have podcastsite.com/:podcastname/:podcastepisode/:comment_id


A cool way of reaping benefits of both approaches is to separate client side and server side URLs.

The thing after the '#', which has been called many things in the past (hash fragment, I guess), never goes into the server, so you can play with it using JavaScript to make it meaningful to users, while keeping it simple and flat on the real HTTP requests.


That's a neat idea. It's like a self-contained documentation / "comment" of the URL. `#` is even fairly well-known commenting character.


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.


> 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)


The data model matters doesn't it?

If it's /org/orgId/blog/blogId and a user has access to only a single orgId and needs to be logged in then doing it in /blog/blogId makes sense.

But if a user has acess to multiple orgs via different orgIds, then it makes more sense to do the former with /org/orgId/blog/blogId because then the page is sharable via URL. If you do just /blog/blogId that prevents a user from easily accessing a new organization (unless you use query parameters)


Just gives blogs unique IDs regardless of the organization. No two blogs ever have the same ID. Now the route doesn't need to provide the Organization ID, because that association lives in the data store for your application and you can infer it.


This is the right answer here. This design simplifies APIs and makes them much more straight forward as it more easily lets you operate on the data.

Nesting entities seems like the right approach at first but can turn into a nightmare as you continue to build things out.


> If you _want_ to have some sort of hierarchy in the URL, you can redirect:

> - /organizations/123/blogs/1234 -> /blogs/1234

And:

/organizations/123/blogs -> /blogs?organization_id=123

Because what you're doing is just applying a filter to an index of blog resources.

I.e. the "/organizations/123/blogs" route is simply an alias to "/blogs?organization_id=123" which you can just redirect to in your gateway.


> - /organizations/:id

> - /blogs/:id

The pragmatic, large-company-only counterpoint is the narrow edge case where:

- :id must be human-readable for "SEO reasons"

- there are many competing organizations and blogs to the point where there may be a name collision.

Although in that case, I'd still suggest:

/:organization-name/:blog-name


What about /blogs/:id?organization=:id, doesn’t it solve the issue?


What if the company changes name?


What if you're using ids and two organisations merge? Whatever solution works for organisation ids will work for organisation names.


I'd also add that using a flat resource hierarchy also makes it slightly simpler to peel off a resource set, such as /organizations/:id, into a dedicated microservice.


Yep this is it. If you nest IDs you just end up burdening all consumers with having to mimic your hierarchical structure when they don't need to.


It's worth noting that hierarchy itself isn't the problem, only hierarchy that isn't necessary for identifying a resource. If you happen to start comments on every post from 0 -- something like a (post_id, comment_id) primary key constraint on your comments table -- then it's natural to have a `/posts/1/comments/2` structure for your URLs. Under this data model, if you just had `/comments/2`, you wouldn't know enough to actually identify the comment -- just that it's the second comment on some post.

Whether it's a good idea to use this kind of composite key is a separate question, though.


That's a poor advice.

APIs are consumed by humans first. Machines don't care about your structure.

HATEOS is the part that actually doesn't matter in practice.


Both machines and humans can easily follow links, that's precisely why hypermedia was invented.

The part for humans is the HTML form I'm typing on. Humans _can_ type in HTTP/1.1 non-chunked requests by hand, but I don't see it that often.

For developers, you might think that the URIs are "the UI" of an API, but they were not designed to be used in that way.

We were meant to use the HTML rel= attribute to inform our machine clients on how to navigate links. We still do, sometimes. rel=stylesheet is a vestigial trace of that. rel=nofollow still informs clients of irrelevant URLs to this day.

We use HATEOAS all the time, we're just not using the standards designed to express them. A complete API schema (what seems to be the cool thing these days), in the eyes of REST, is nothing but a giant complex hypermedia form written in an unspecified media type. The "blog_id" is nothing but a lofi link, and so on.


This seems like the most traditional and respectful of REST way to go.

You could try a HATEOAS approach if you want to get a bit more fancy.

https://restfulapi.net/hateoas/

If you are following REST strictly there should be only objects, actions and verbs.


> If you are following REST strictly there should be only objects, actions and verbs.

Not quite. That would be a resource-based architectures much like REST, but REST dials requirements a notch higher, with stuff like adding capabiliy-discovering semantics to resources such as navigation links to related resources and operations to express it's state and state-changed, in the form of HATEOAS.


I can think of at least one example where this wouldn't work: when blog ids are not unique across organizations.


This would be my recommendation, especially if you use UUIDs or some other long string as identifiers




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: