TE
科技回声
首页24小时热榜最新最佳问答展示工作
GitHubTwitter
首页

科技回声

基于 Next.js 构建的科技新闻平台,提供全球科技新闻和讨论内容。

GitHubTwitter

首页

首页最新最佳问答展示工作

资源链接

HackerNews API原版 HackerNewsNext.js

© 2025 科技回声. 版权所有。

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

68 点作者 emschwartz将近 3 年前
If you&#x27;re building a REST&#x2F;HTTP API, how do you think about when to express the hierarchy of resources in the URLs?<p>For example, let&#x27;s say you had a blog site. Organizations have blogs. Blogs are made up of sections and comment threads. Comment threads have individual comments.<p>Would you opt for:<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId OR &#x2F;blogs&#x2F;:blogId (and get the organization from somewhere else like an auth token)<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId&#x2F;sections&#x2F;:sectionId OR &#x2F;blogs&#x2F;:blogId&#x2F;sections&#x2F;:sectionId OR &#x2F;sections&#x2F;:sectionId<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId&#x2F;threads&#x2F;:threadId&#x2F;comments&#x2F;:commentId... you get the idea

22 条评论

alganet将近 3 年前
Just do:<p>- &#x2F;organizations&#x2F;:id<p>- &#x2F;blogs&#x2F;:id<p>- &#x2F;sections&#x2F;:id<p>- &#x2F;threads&#x2F;:id<p>- &#x2F;comments&#x2F;:id<p>Why?<p>What determines how resources are related are links, not patterns in the URL. It&#x27;s a graph, the URLs are just nodes, the links are what connect them.<p>If you _want_ to have some sort of hierarchy in the URL, you can redirect:<p>- &#x2F;organizations&#x2F;123&#x2F;blogs&#x2F;1234 -&gt; &#x2F;blogs&#x2F;1234<p>Then each resource expresses how it is related to others using links (hrefs, or other mechanism for orther media types).<p>URIs can be anything like &#x2F;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).<p>For machine APIs, payloads could use &quot;comment_id&quot;, &quot;thread_id&quot; 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).
评论 #32517358 未加载
评论 #32517818 未加载
评论 #32517732 未加载
评论 #32514827 未加载
评论 #32517371 未加载
评论 #32517344 未加载
评论 #32517750 未加载
评论 #32517349 未加载
评论 #32518025 未加载
评论 #32517837 未加载
评论 #32529940 未加载
评论 #32517986 未加载
jraph将近 3 年前
A problem with nested URLs like &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId&#x2F;sections&#x2F;:sectionId is that when you are manipulating a section from the frontend, you don&#x27;t only need to keep and pass around their sectionId, but also their organisationId and blogId everywhere. Often you&#x27;ll already have those, but sometimes not and it&#x27;ll be annoying, you&#x27;ll have to make more complex code with functions taking more parameters than necessary. From the backend, you&#x27;ll need to check that all those ids are consistent and match.<p>It&#x27;s also possible you will need at some point to re-use sub objects in a new context, so you&#x27;ll have different kinds of paths to query those sub objects.<p>I&#x27;d go with Python&#x27;s guideline &quot;Flat is better than nested&quot; here, and in doubt, follow it.<p>I think nested is only appropriate if you want to ensure the requester knows the oragnisationId, the blogId and the threadId of a particular comment to access it. Otherwise, it will lead to more complexity everywhere.
评论 #32518328 未加载
bbassett将近 3 年前
While things often have parents and children, they also very frequently stand alone. Also, sometimes you don&#x27;t want to leak private data (org id&#x27;s) in public content (a blog post)<p>What I recommend is follow the resource standards from something like jsonapi, and organize your routes like this:<p>&#x2F;organizations&#x2F;:organization_id<p>&#x2F;organizations&#x2F;:organization_id&#x2F;blogs -&gt; returns a list of links to blogs (maybe including the very most basic data, like blog title and byline)<p>&#x2F;blogs&#x2F;:blog_id -&gt; one blog, has a link to organization, for going &quot;up&quot; the hirearchy, but only returned for authenticated users (if that&#x27;s necessary in your use case)<p>&#x2F;blogs&#x2F;:blog_id&#x2F;sections -&gt; links to list of sections<p>&#x2F;blogs&#x2F;:blog_id&#x2F;threads -&gt; links to list of threads<p>&#x2F;threads&#x2F;:thred_id -&gt; one thread, but links to parents as above<p>&#x2F;sections&#x2F;:section_id -&gt; same as threads<p>you get the idea.<p>I&#x27;ve done it this way and I feel like it ends up being very easy to wrap my head around where things are, and how they relate to one another.<p>One of my favorite example APIs is stripe. the layout makes sense, routes make sense, and it&#x27;s a very complex system which becomes easy to understand and the routes aren&#x27;t insanity, if you are looking for a very public example
eyelidlessness将近 3 年前
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 <i>links</i> (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.<p>You can do the same even if some entry points are known, and then I&#x27;d follow the approach of &#x2F;top-level&#x2F;:id, because it’s at least a predictable way to start. If further API access requires discovery, go wild and format your URLs how it works best for your service. Just keep them unique and stable, because stable URLs are cool!
评论 #32518383 未加载
jackconsidine将近 3 年前
The Github API [0] is a good place to look- it seems like they thought a lot about their hierarchies.<p>They do things like<p>&#x2F;orgs&#x2F;ORG&#x2F;repos &#x2F;repos&#x2F;OWNER&#x2F;REPO&#x2F;issues<p>In other words, GH scopes their endpoints usually one <i>maybe</i> two layers. I think conceptually that makes a lot of sense<p>[0] <a href="https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;rest&#x2F;repos&#x2F;repos#list-organization-repositories" rel="nofollow">https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;rest&#x2F;repos&#x2F;repos#list-organizatio...</a>
评论 #32518134 未加载
chrismorgan将近 3 年前
&gt; <i>&#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId OR &#x2F;blogs&#x2F;:blogId (and get the organization from somewhere else like an auth token)</i><p>You make it sound like the blog posts are keyed by (organizationId, blogId). If this is so, then &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId is clearly superior. But if blogId alone is sufficient to identify the blog post, then &#x2F;blogs&#x2F;:blogId is very likely to be wiser.<p>But you also make it sound like sessions might be scoped to an organization, that organization is, for API purposes, a singleton. In that case, &#x2F;organizations&#x2F;:organizationId (the route, not the prefix) would not make a great deal of sense: it should either be just &#x2F;organization or not exist, and be removed as a prefix from other things.<p>This reasoning can be applied to the rest as well.
danjames_131将近 3 年前
In theory, HATEOS is designed to resolve this problem.<p>Quick 3 min draft example: <a href="https:&#x2F;&#x2F;gist.github.com&#x2F;djames-bloom&#x2F;2131d021a8f6d3d09c5a808626575a58" rel="nofollow">https:&#x2F;&#x2F;gist.github.com&#x2F;djames-bloom&#x2F;2131d021a8f6d3d09c5a808...</a>
评论 #32507563 未加载
jabbany将近 3 年前
One way to think about it might be to think of REST as RPC rather than folder nesting. E.g. `&#x2F;[param_name]&#x2F;[value]&#x2F;..`<p>So if you can find blogs just by their blogId, then `&#x2F;blogs&#x2F;:blogId` makes sense since you only provide the necessary parameters. `&#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId` would imply that `blogId` is not unique across organizations. This may also make sense if that&#x27;s how it is.
claytongulick将近 3 年前
So, I generally do it the nested way: organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId<p>The other post on this thread are accurate, in that normally keeping it simpler is better, but in this case I disagree.<p>With the nested approach you have the ability to do multiple levels of validation and mitigate a lot of key guessing attacks.<p>Imagine a different scenario:<p>facility&#x2F;:facility_id&#x2F;provider&#x2F;:provider_id&#x2F;patient&#x2F;:patient_id<p>Vs<p>patient&#x2F;:patient_id<p>In the first case there are three tokens I&#x27;d need to possess or guess in order to access a patient record. In the second, there&#x27;s just one.<p>The implementation will still only grab the patient id for the query, but it&#x27;ll validate that the two other keys are correct as well.<p>It also makes role scoping easier. I.e. my role grants admin access to all patients in a facility. Middleware to validate the access is a lot easier to implement if you have all those keys in the query string. Declarative permissions on the routes are a lot easier.
theptip将近 3 年前
If it’s strictly a tree of relationships (always 1:many) then there are some benefits to having nested IDs. You can do things like attach permissions to the root of the tree, and logging is often clearer (the owner is right there in the URL).<p>However as soon as you need a M:M or many:1 relationship this breaks down. If a blog can be shared between multiple orgs, how do you represent that?<p>Therefore as others have recommended, a single ID per URL with a sub-endpoint for the nested list like &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F; (but no two-ID endpoints) is the generalizable and future-proof option. I strongly prefer to keep all the state in the URL rather than hitting &#x2F;blogs&#x2F; and getting the org implicitly from the auth state. Way harder to debug if the auth context can change what results your API produces.
jmathai将近 3 年前
Consider using something like <a href="https:&#x2F;&#x2F;google.aip.dev&#x2F;" rel="nofollow">https:&#x2F;&#x2F;google.aip.dev&#x2F;</a>.<p>Consistency across an API surface is more valuable than local optimizations.
vermon将近 3 年前
In case the organisation is basically a tenant in multi-tenant application I would leave it out of the API and resolve it in another way, but if it is not I would leave it in. Does that make sense?
评论 #32517343 未加载
评论 #32507528 未加载
sandreas将近 3 年前
Why reinvent the wheel? Just use a common standard... I prefer <a href="https:&#x2F;&#x2F;jsonapi.org&#x2F;" rel="nofollow">https:&#x2F;&#x2F;jsonapi.org&#x2F;</a>.
评论 #32517674 未加载
antifa将近 3 年前
I find that having both &#x2F;foo&#x2F;:fooId and &#x2F;bar&#x2F;:barId&#x2F;foo&#x2F;:fooId to basically result in duplicate code&#x2F;tests or unnecessary complexity. I&#x27;ll allow &#x2F;bar&#x2F;:barId&#x2F;foo&#x2F;:fooId in an SPA router though (not http API). In probably most frameworks, each additional route probably encures a microscopic RAM&#x2F;CPU cost.
jongjong将近 3 年前
The long form is more flexible because it gives you the full information about the state of the app. If you don&#x27;t include the blogId in the URL, the front end will not know which blog the section or comment belongs to until it has finished loading from the server... So in the case of a single-page app, you can&#x27;t show a &#x27;Go back to blog&#x27; button in the UI until the page has finished loading... Though it&#x27;s trivial to do if the blogId is in the URL.<p>A common pattern I follow with URLs is that I sometimes allow the user to omit the final ID in the URL; in this case I do a front-end redirect to the URL with the ID of the main&#x2F;default resource appended at the end - This is useful if the user account has (for example) a &#x27;main blog&#x27; associated with it.
mLuby将近 3 年前
If average users will see the URL in their browser, make it meaningful. After all, it is a user interface.<p><pre><code> X blog.com&#x2F;users&#x2F;1234&#x2F;posts&#x2F;5678 √ blog.com&#x2F;tom-nook&#x2F;thoughts-about-urls</code></pre>
评论 #32521655 未加载
tasubotadas将近 3 年前
Go for logical structure and your query patterns.<p>If you plan querying blogs per organization, then make it a su resource under the organization.<p>If you are planning only to get a full list, go for root level blogs.<p>If you are planning to do both then do both :-)<p><a href="https:&#x2F;&#x2F;dev.tasubo.com&#x2F;2021&#x2F;08&#x2F;quick-practical-introduction-to-restful-apis-and-interfaces.html#resources-should-have-well-defined-locations-uris" rel="nofollow">https:&#x2F;&#x2F;dev.tasubo.com&#x2F;2021&#x2F;08&#x2F;quick-practical-introduction-...</a>
mountainriver将近 3 年前
Biggest issues with nested URLs are:<p>* 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<p>* harder to version the data models, if I need to add path versioning I’m stuck versioning and breaking the entire tree
yomkippur将近 3 年前
I seriously think we need a serious update to REST api urls. There is no rule that says it has to be this hard.<p>I would imagine something like graphql type of URL schema:<p><pre><code> api.xxx.com&#x2F;{organizations: organizationId { blogs: blogId { sections: sectionId } } }</code></pre>
评论 #32517966 未加载
rvdginste将近 3 年前
I would go for the hierarchical urls, like:<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId&#x2F;sections&#x2F;:sectionId<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId&#x2F;threads&#x2F;:threadId&#x2F;comments&#x2F;:commentId<p>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 following CRUD endpoints:<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs, with GET and POST to retrieve a list of blogs belonging to an organization and to create a blog for an organisation<p>- &#x2F;organizations&#x2F;:organizationId&#x2F;blogs&#x2F;:blogId, with GET, PUT and DELETE to retrieve, update and remove a blog<p>The fact that you always have the :organizationId in the url likely means that you can easily verify authorization, because I assume you would have a link between the user and an organization and maybe that link would already be available in the access token.<p>I don&#x27;t think it is a problem that the urls are heavily nested. The urls are not supposed to be manually entered, but are using from within an application. I like the use of HATEOAS links, because I don&#x27;t really like the frontend trying to assemble urls by itself.<p>Also note that next to the hierarchical links, you might need some extra for search that are not hierarchical. For example, say that there is a need to search blogs across organizations. This could be provided using the following:<p>- &#x2F;blogs?description=test&amp;language=en<p>You can provide a search endpoint in the root for blogs. This endpoint could then return some kind of blog object with a reference to the actual blog location (like &#x2F;organizations&#x2F;34&#x2F;blogs&#x2F;11). When you use this approach, the frontend has no need to assemble urls and the structure or complexity of the url does not matter. Do note that the frontend still needs some generic &quot;start&quot; urls like the blogs search endpoint.<p>Additional advantage (like others have mentioned), is that with the hierarchical urls it becomes more difficult to &quot;guess&quot; an url. Altough, if you want to properly protect against that, you should transform the ids before putting them in the url and transform them back when reading them out of an incoming url, for example using symmetric encryption with a secret only known in the backend.
评论 #32521604 未加载
endtime将近 3 年前
<a href="http:&#x2F;&#x2F;aip.dev" rel="nofollow">http:&#x2F;&#x2F;aip.dev</a> describes Google&#x27;s approach to this.
willcipriano将近 3 年前
I&#x27;d do :organizationId&#x2F;:blogId&#x2F;:postId&#x2F;:commentId&#x2F;:threadId leaving out the description of what every value is for shorter urls. I think you can get away with this how those relationships are defined.<p>example.org&#x2F;2&#x2F;1&#x2F;4&#x2F;12&#x2F;326