ChatGPT解决这个技术问题 Extra ChatGPT

What are best practices for REST nested resources?

As far as I can tell each individual resource should have only one canonical path. So in the following example what would good URL patterns be?

Take for example a rest representation of Companies. In this hypothetical example, each company owns 0 or more departments and each department owns 0 or more employees.

A department can't exist without an associated company.

An employee can't exist without an associated department.

Now I'd find the natural representation of the resource patterns to be.

/companies A collection of companies - Accepts POST for a new company. Get for the entire collection.

/companies/{companyId} An individual company. Accepts GET, PUT and DELETE

/companies/{companyId}/departments Accepts POST for a new item. (Creates a department within the company.)

/companies/{companyId}/departments/{departmentId}/

/companies/{companyId}/departments/{departmentId}/employees

/companies/{companyId}/departments/{departmentId}/employees/{empId}

Given the constraints, in each of the sections, I feel that this makes sense if a bit deeply nested.

However, my difficulty comes if I want to list (GET) all employees across all companies.

The resource pattern for that would most closely map to /employees (The collection of all employees)

Does that mean that I should have /employees/{empId} also because if so then there are two URI's to get the same resource?

Or maybe the entire schema should be flattened but that would mean that employees are a nested top-level object.

At a basic level /employees/?company={companyId}&department={deptId} returns the exact same view of employees as the most deeply nested pattern.

What's the best practice for URL patterns where resources are owned by other resources but should be query-able separately?

This is almost exactly the oppsite problem to that described in stackoverflow.com/questions/7104578/… though the answers may be related. Both questions are about ownership but that example implies that the top level object isn't the owning one.
Exactly what I was wondering about. For the given use case your solution seems fine, but what if the relation is an aggregation rather than a composition? Still struggling to figure out what the best practice is here... Also, does this solution imply only the creation of the relationship, e.g. an existing person is employed or does it create a person object?
It creates a person in my fictitious example. The reason I used those domain terms is its a reasonably understandable example, though mimicking my actual problem. Have you looked through the linked question that may halp you more for an aggragation relationship.
I've split my question into an answer and a question.

P
Patc

I've tried both design strategies - nested and non-nested endpoints. I've found that:

if the nested resource has a primary key and you don't have its parent primary key, the nested structure requires you to get it, even though the system doesn't actually require it. nested endpoints typically require redundant endpoints. In other words, you will more often than not, need the additional /employees endpoint so you can get a list of employees across departments. If you have /employees, what exactly does /companies/departments/employees buy you? nesting endpoints don't evolve as nicely. E.g. you might not need to search for employees now but you might later and if you have a nested structure, you have no choice but to add another endpoint. With a non-nested design, you just add more parameters, which is simpler. sometimes a resource could have multiple types of parents. Resulting in multiple endpoints all returning the same resource. redundant endpoints makes the docs harder to write and also makes the api harder to learn.

In short, the non-nested design seems to allow a more flexible and simpler endpoint schema.


Was very refreshing to come across this answer. I have been using nested endpoints for several months now after being taught that was the "right way". I came to all of the same conclusions you listed above. So much easier with a non-nested design.
You seem to list some of the downsides as upsides. "Just cram more parameters into a single end-point" makes the API harder to document and learn, not the other way around. ;-)
Not a fan of this answer. There's no need to introduce redundant endpoints just because you've added a nested resource. It's also not a problem to have the same resource returned by multiple parents, provided those parents genuinely own the nested resource. It's not a problem to get a parent resource to learn how to interact with the nested resources. A good discoverable REST API should do this.
@Scottm - One drawback of nested resources that I came across is that it could lead to returning incorrect data if the parent resource ids are incorrect/mismatch. Assuming there are no authorization issues, it is left upto the api implementation to verify that the nested resource is indeed a child of the parent resource that is passed. If this check is not coded for, the api response could be incorrect leading to corruption. What are your thoughts?
You don't need the intermediate parent ids if the end resources all have unique ids. Eg, to get the employee by id you have GET /companies/departments/employees/{empId} or to get all employees in company 123 you have GET /companies/123/departments/employees/ Keeping the path hierarchical makes it more apparent how you can get to the intermediate resources to filter/create/modify and helps with discoverability in my opinion.
J
Joakim

What you have done is correct. In general there can be many URIs to the same resource - there are no rules that say you shouldn't do that.

And generally, you may need to access items directly or as a subset of something else - so your structure makes sense to me.

Just because employees are accessible under department:

company/{companyid}/department/{departmentid}/employees

Doesn't mean they can't be accessible under company too:

company/{companyid}/employees

Which would return employees for that company. It depends on what is needed by your consuming client - that is what you should be designing for.

But I would hope that all URLs handlers use the same backing code to satisfy the requests so that you aren't duplicating code.


This is pointing out the spirit of RESTful, there are no rules that say you should or should not do if only you consider a meaningful resource first. But further, I wonder what's the best practice for not duplicating code in such scenarios.
@abookyun if you need both routes, then repeated controller code between them can be abstracted to service objects.
This has nothing to do with REST. REST does not care about how you structure the path part of your URLs... all it cares about is valid, hopefully durable URIs...
Driving at this answer, I think any api where the dynamic segments are all unique identifiers shouldn't need to handle multiple dynamic segments (/company/3/department/2/employees/1). If the api provides ways to get each resource, then making each of those requests could be done in either a client side library or as a one-off endpoint that reuses code.
While there is no prohibition, I consider it more elegant to have only one path to a resource - keeps all mental models simpler. I also prefer that URIs don't change their resource type if there is any nesting. for example /company/* should only return the company resource and not change resource type at all. None of this is specified by REST - its generally a poorly specified - just personal preference.
W
Wes

I've moved what I've done from the question to an answer where more people are likely to see it.

What I've done is to have the creation endpoints at the nested endpoint, The canonical endpoint for modifying or querying an item is not at the nested resource.

So in this example (just listing the endpoints that change a resource)

POST /companies/ creates a new company returns a link to the created company.

POST /companies/{companyId}/departments when a department is put creates the new department returns a link to /departments/{departmentId}

PUT /departments/{departmentId} modifies a department

POST /departments/{deparmentId}/employees creates a new employee returns a link to /employees/{employeeId}

So there are root level resources for each of the collections. However the create is in the owning object.


I have come up with the same type of design as well. I think it's intuitive to create things like this "where they belong", but then still be able to list them globally. Even more so when there's a relationship where a resource MUST have a parent. Then creating that resource globally does not make that obvious, but doing it in a sub-resource like this makes perfect sense.
I guess you used POST meaning PUT, and otherwise.
Actually no Note that I'm not using pre assigned Ids for creation as the server in this case is responsible for returning the id (in the link). Therefore writing POST is correct (can't do a get on the same implementation). The put however changes the entire resource but its still available at the same location so I PUT it. PUT vs POST is a different matter and is controversial too. For example stackoverflow.com/questions/630453/put-vs-post-in-rest
@Wes Even I prefer modifying verb methods to be under the parent. But, do you see passing query parameter for global resource is accepted well? Ex : POST /departments with query parameter company=company-id
@Mohamad If you think that the other way is easier both in understanding and in applying constraints then feel free to give an answer. Its about making the mapping explicit in this case. It could work with a parameter but really thats what the question is. What is the best way.
L
Long Nguyen

I've read all of the above answers but it seems like they have no common strategy. I found a good article about best practices in Design API from Microsoft Documents. I think you should refer.

In more complex systems, it can be tempting to provide URIs that enable a client to navigate through several levels of relationships, such as /customers/1/orders/99/products. However, this level of complexity can be difficult to maintain and is inflexible if the relationships between resources change in the future. Instead, try to keep URIs relatively simple. Once an application has a reference to a resource, it should be possible to use this reference to find items related to that resource. The preceding query can be replaced with the URI /customers/1/orders to find all the orders for customer 1, and then /orders/99/products to find the products in this order.

.

Tip Avoid requiring resource URIs more complex than collection/item/collection.


The reference you give is amazing along with the point you stand out of not making complex URIs.
So when I want to create a team for a user, should it be POST /teams (userId in thebody) or POST /users/:id/teams
@coinhndp Hi, You should use POST /teams and you could get userId after authorizing access token. I mean when you create a stuff you need authorization code, right? I don't know what framework are you using but I'm sure you could get userId in API controller. For example: In ASP.NET API, call RequestContext.Principal from within a method on ApiController. In Spring Secirity, SecurityContextHolder.getContext().getAuthentication().getPrincipal() will help you. In AWS NodeJS Lambda, that is cognito:username in headers object.
So what's wrong with the POST /users/:id/teams. I think it is recommended in the Microsoft Document that you posted above
@coinhndp If you create team as admin, that's good. But, as normal users, I don't know why you need userId in path? I suppose that we have user_A and user_B, what do you think if user_A could create a new team for user_B if user_A call POST /users/user_B/teams. So, no need to pass userId in this case, userId could get after authorization. But, teams/:id/projects is good to make a relation between team & project for instance.
M
Maxime Laval

I disagree with this kind of path

GET /companies/{companyId}/departments

If you want to get departments, I think it's better to use a /departments resource

GET /departments?companyId=123

I suppose you have a companies table and a departments table then classes to map them in the programming language you use. I also assume that departments could be attached to other entities than companies, so a /departments resource is straightforward, it's convenient to have resources mapped to tables and also you don't need as many endpoints since you can reuse

GET /departments?companyId=123

for any kind of search, for instance

GET /departments?name=xxx
GET /departments?companyId=123&name=xxx
etc.

If you want to create a department, the

POST /departments

resource should be used and the request body should contain the company ID (if the department can be linked to only one company).


To me, this is an acceptable approach only if the nested object makes sense as an atomic object. If they are not, It wouldnt really make sense to break them apart.
This is what I said, if you also want to be able to retrieve departments, meaning if you'll use a /departments endpoint.
It may also make sense to allow departments to be included via lazy loading when fetching a company, eg GET /companies/{companyId}?include=departments, since this allows both the company and its departments to be fetched in a single HTTP request. Fractal does this really well.
When you're setting up acls you probably want to restrict the /departments endpoint to only be accessible by an admin, and have each company access their own departments only through ` /companies/{companyId}/departments`
@MatthewDaly OData also does that nicely with $expand
r
redben

How your URLs look have nothing to do with REST. Anything goes. It actually is an "implementation detail". So just like how you name your variables. All they have to be is unique and durable.

Don't waste too much time on this, just make a choice and stick to it/be consistent. For example if you go with hierarchies then you do it for all your resources. If you go with query parameters...etc just like naming conventions in your code.

Why so ? As far as I know a "RESTful" API is to be browsable (you know..."Hypermedia as the Engine of Application State"), therefore an API client does not care about what your URLs are like as long as they're valid (there's no SEO, no human that needs to read those "friendly urls", except may be for debugging...)

How nice/understandable a URL is in a REST API is only interesting to you as the API developer, not the API client, as would the name of a variable in your code be.

The most important thing is that your API client know how to interpret your media type. For example it knows that :

your media type has a links property that lists available/related links.

Each link is identified by a relationship (just like browsers know that link[rel="stylesheet"] means its a style sheet or rel=favico is a link to a favicon...)

and it knowns what those relationships mean ("companies" mean a list of companies,"search" means a templated url for doing a search on a list of resource, "departments" means departments of the current resource )

Below is an example HTTP exchange (bodies are in yaml since it's easier to write):

Request

GET / HTTP/1.1
Host: api.acme.io
Accept: text/yaml, text/acme-mediatype+yaml

Response: a list of links to main resource (companies, people, whatever...)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:04:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: this is your API's entrypoint (like a homepage)  
links:
  # could be some random path https://api.acme.local/modskmklmkdsml
  # the only thing the API client cares about is the key (or rel) "companies"
  companies: https://api.acme.local/companies
  people: https://api.acme.local/people

Request: link to companies (using previous response's body.links.companies)

GET /companies HTTP/1.1
Host: api.acme.local
Accept: text/yaml, text/acme-mediatype+yaml

Response: a partial list of companies (under items), the resource contains related links, like link to get the next couple of companies (body.links.next) an other (templated) link to search (body.links.search)

HTTP/1.1 200 OK
Date: Tue, 05 Apr 2016 15:06:00 GMT
Last-Modified: Tue, 05 Apr 2016 00:00:00 GMT
Content-Type: text/acme-mediatype+yaml

# body: representation of a list of companies
links:
  # link to the next page
  next: https://api.acme.local/companies?page=2
  # templated link for search
  search: https://api.acme.local/companies?query={query} 
# you could provide available actions related to this resource
actions:
  add:
    href: https://api.acme.local/companies
    method: POST
items:
  - name: company1
    links:
      self: https://api.acme.local/companies/8er13eo
      # and here is the link to departments
      # again the client only cares about the key department
      department: https://api.acme.local/companies/8er13eo/departments
  - name: company2
    links:
      self: https://api.acme.local/companies/9r13d4l
      # or could be in some other location ! 
      department: https://api2.acme.local/departments?company=8er13eo

So as you see if you go the links/relations way how you structure the path part of your URLs does't have any value to your API client. And if your are communicating the structure of your URLs to your client as documentation, then your are not doing REST (or at least not Level 3 as per "Richardson's maturity model")


"How nice/understandable a URL is in a REST API is only interesting to you as the API developer, not the API client, as would the name of a variable in your code be." Why would this NOT be interesting? This is very important, if anyone but yourself is also using the API. This is part of the user experience, so I would say it is very important that this is easy to understand for the API client developers. Making things even more easy to understand by linking resources clearly is of course a bonus (level 3 in the url you provide). Everything should be intuitive and logical with clear relations.
@Joakim If you are making a level 3 rest API (Hypertext As The Engine Of Application State), then the url's path structure is absolutely of no interest to the client (as long as it is valid). If you are not aiming for level 3, then yes, it is important and should be guessable. But real REST is level 3. A good article: martinfowler.com/articles/richardsonMaturityModel.html
I object to ever creating an API or UI that is not user friendly for human beings. Level 3 or not, I agree linking resources is a great idea. But to suggest doing so "makes it possible to change URL scheme" is to be out of touch with reality, and how people use APIs. So it's a bad recommendation. But sure in the best of all worlds everyone would be at Level 3 REST. I incorporate hyperlinks AND use a humanly understandable URL scheme. Level 3 does not exclude the former, and one SHOULD care in my opinion. Good article though :)
One should of course care for the sake of maintainability and other concerns, I think you miss the point of my answer : the way the url looks does not deserve a lot of thinking and you should "just make a choice and stick to it/be consistent" as I said in the answer. And in the case of a REST API, at least my opinion, user friendlyness is not in the url, it is mostly in (the media type) Anyway I hope you understand my point :)
p
partydrone

Rails provides a solution to this: shallow nesting.

I think this is a good because when you deal directly with a known resource, there's no need to use nested routes, as has been discussed in other answers here.


You should actually provide the part of the blog which answers the question and provide links for reference.
@reoxey, the text "shallow nesting" links to the Rails documentation that explains shallow nesting. Does it not work?
The major problem with the link is that it takes you to partway through an example, and is not language agnostic... I don't know Ruby, and don't understand what the code in the example is actually doing, therefore, unless I'm willing to fully study the lengthy document, learn some Ruby, and then learn some Rails, it's of no use to me. This answer should summarise the technique that the article/manual describes, in pseudocode/structured English, to better express what you're suggesting here.
u
user566245

as per django rest framework documentation:

Generally we recommend a flat style for API representations where possible, but the nested URL style can also be reasonable when used in moderation.

https://www.django-rest-framework.org/api-guide/relations/#example_2