Live data from Hacker News

Ask HN: Nested Resources in REST/HTTP API URLs?

news.ycombinator.com

61–70 of 72 posts

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#61
post #23
post #16

Earlier quoted context omitted.

> - /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 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.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#62
post #58

It doesn’t actually matter unless your API is expected to be consumed without any particular entry point. If you can expect users to enter at specific points, you can represent state as hypertext and give them links (URLs) rather than ids. They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. You can do the same even if some entry points are known, and then I'd…

> They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. This is REST. As a backend dev I've attempted this several times, and the consumers of my API never cared about the hyperlinks. I've tried explaining that the 'delete' hyperlink is only going to be present when the user is authorized to delete the resource, and isn't that wonderful and making your life easi…

Yeah everyone is fine with hardcoding API URLs, even submitting the resulting client to an app store.

You're not going to fix that unless you (initially) make the URLs unpredictable (that is, make the links the only way to know a URL).

But that will (initially) be costly to do. And is probably annoying to your API consumers as well.

So only consider this if you need reduced coupling and there is clear value in avoiding hardcoded URLs.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#63
post #16
post #3

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 oth…

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

[deleted]

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#64
post #56

Earlier quoted context omitted.

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…

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 pa…

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

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#65

I would go for the hierarchical urls, like: - /organizations/:organizationId/blogs/:blogId - /organizations/:organizationId/blogs/:blogId/sections/:sectionId - /organizations/:organizationId/blogs/:blogId/threads/:threadId/comments/:commentId I do this because it expresses the structure and the constraints of the data. A blog cannot exist without being linked to an organisation. And then you typically have the follow…

> - /organizations/:organizationId/blogs, with ... POST to ... create a blog for an organisation

Be careful with that! If there's a network error sending the response, the blog will have been created and you won't have received the ID on the client.

In a distributed environment (such as REST) it's better to make all calls idempotent. Choose an ID on the client e.g. generate a random UUID. Then do a PUT to ..../:id. That will update the blog if it exists or create it if not. If the create call fails, you can just retry it (with the same ID), if the request previously failed and it never got created it'll create it now, and if the response previously failed and it did get created but you never got the successful response it'll get "edited" to the same values as it already has.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#66
post #34

If average users will see the URL in their browser, make it meaningful. After all, it is a user interface. X blog.com/users/1234/posts/5678 √ blog.com/tom-nook/thoughts-about-urls

Be aware that meaningful names such as "thoughts-about-urls" might change (e.g. if you rename the article) in a way that meainingless IDs will not.

If you want your URLs to continue to work then you either need to (a) keep using the previous URL, meaning it's no longer meaningful to the content, (b) change the URL to match the new content, meaning existing links break or (c) support the old and the new versions with a redirection mechanism which is a bunch of effort.

Not saying you shouldn't use meaningful URLs but make sure you're going into the decision with your eyes open and preparing to accept the downsides.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#67
Biggest issues with nested URLs are:

* may be harder to route if you break your app into services, you are now relying on the server implementation to have fast regex routing

* harder to version the data models, if I need to add path versioning I’m stuck versioning and breaking the entire tree

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#68
post #58

It doesn’t actually matter unless your API is expected to be consumed without any particular entry point. If you can expect users to enter at specific points, you can represent state as hypertext and give them links (URLs) rather than ids. They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. You can do the same even if some entry points are known, and then I'd…

> They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. This is REST. As a backend dev I've attempted this several times, and the consumers of my API never cared about the hyperlinks. I've tried explaining that the 'delete' hyperlink is only going to be present when the user is authorized to delete the resource, and isn't that wonderful and making your life easi…

> As a backend dev I've attempted this several times, and the consumers of my API never cared about the hyperlinks. […] Anyone has experience with this going the same or differently?

I’ve seen it work exceedingly well with a browsable interface, like the one provided by Django REST Framework (not an endorsement of the framework per se; it does a lot of stuff very nicely, but other stuff can be a pain). If the API is human-discoverable, that makes the appeal of hyperlinks (actually linked in the browsable interface) a lot more obvious.

> I've tried explaining that the 'delete' hyperlink is only going to be present when the user is authorized to delete the resource, and isn't that wonderful and making your life easier? It turns out, (my) API consumers want to hardcode URLs on their side and have the payload include a can-delete flag. Same across several companies.

Not to nitpick the RESTfulness of this design, but I think it’s interesting that a more purist HATEOAS approach would be closer to what they wanted. Example response:

  HTTP/1.1 200 OK
  Allow: DELETE, GET, PATCH, PUT
  Content-Type: application/json
  …

  {
    "url": "https://example.com/foos/any-key",
    …
  }
Note `Allow`, that’s a standard HTTP header many clients can parse and understand automatically, and might even give them their can-delete flag for free. They can hard code the URL if they like, but they already have the URL because it’s the same one, for the same resource they already requested. Or, if they requested a listing… sure, again, they can hard-code the listed resources but I think the convenience of including URLs is a probably a lot more obviously appealing there.

Coming back to DRF’s browsable interface, it similarly uses standard HTTP semantics, in this case content negotiation. If it receives a request with `Accept: text/html` it responds with a web representation of whichever resource you requested. If the request is authorized to perform other actions on that resource—determined by the same `Allow` header—the web representation also includes a form to perform those actions.

This isn’t to say everyone will see the appeal! But in my experience it’s a lot easier to grasp when an API really does embrace HATEOAS.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#69
post #58

It doesn’t actually matter unless your API is expected to be consumed without any particular entry point. If you can expect users to enter at specific points, you can represent state as hypertext and give them links (URLs) rather than ids. They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. You can do the same even if some entry points are known, and then I'd…

> They don’t need to know or care about your URL patterns, they just need to follow the hyperlinks you provide. This is REST. As a backend dev I've attempted this several times, and the consumers of my API never cared about the hyperlinks. I've tried explaining that the 'delete' hyperlink is only going to be present when the user is authorized to delete the resource, and isn't that wonderful and making your life easi…

>consumers of my API never cared about the hyperlinks.

Exact same experience. Advocating for HATEOS actually confused developers in my org. In the end, I had precisely zero consumers not hardcode URLs client-side.

I was pushing on my team for us to adopt more generic client-side code so that we could iterate on web UIs quicker but just couldn't get buy-in. I had a basic collection+json web client that I demo'd for browsing various services that returned that hypermedia type but people just weren't interested.

It was (and still is) the case that most of our internal apps fit the "simple collection" data and manipulation model. I probably blew it by not doing any nice css styling of the demo web client.

I stay from the web app space internally now. If I'm being honest, my org's web projects mostly fail these days. The common pattern goes something like legacy desktop app -> web app generates excitement (modern UI! node.js! CSS improvements!), dev starts, dev continues for indeterminate time, users lose interest and buy external tools, team inherits other legacy desktop apps, dev team adopts new legacy app to repeat on.

I'm embarrassed about my teams' results in this general space.

Re: Ask HN: Nested Resources in REST/HTTP API URLs?

#70
post #64
post #56

Earlier quoted context omitted.

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 pa…

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)

Post reply on HN