What are best practices for REST nested resources?
Asked Answered
R

8

421

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?

Rh answered 6/1, 2014 at 13:51 Comment(5)
This is almost exactly the oppsite problem to that described in #7105078 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.Rh
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?Smug
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.Rh
I've split my question into an answer and a question.Rh
Here is an interesting article on this topic moesif.com/blog/technical/api-design/…Haskel
D
195

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.

Dicarlo answered 15/10, 2014 at 17:11 Comment(9)
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.Whitman
@Whitman if you need both routes, then repeated controller code between them can be abstracted to service objects.Tide
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...Proser
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.Casias
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.Weasand
In the given example, would you expect company/{companyid}/employees to return a JSOn representation of the employees with the given company ID, or would you also expect to see a JSON representation of the company?Heyerdahl
to have the employees of all the company, I woud use company/{companyid}/departments/employees rather that company/{companyid}/employees and alse. the naming between the resources should be homogenuous between plural or singular. which means either we use demartments and employees (both with s) or department and employeeAngiology
I agree, this should be handled by making multiple requests. A good pattern would be to use a BFF (backend for frontend), i.e. a facade API, to simplify client logic.Witted
Although I generally agree with this answer, enabling the endpoint company/{companyid}/department/{departmentid}/employees introduces several problems that could be avoid entirely if the functionality was instead mapped to the truncated /department/{departmentid}/employees. 1) It forces the API client to know the companyId of the department's owning company, despite this being unnecessary. 2) It introduces ambiguity if the given departmentId is not owned by the companyId. Indeed, there is no reason to insist that the top level containment in the relationship prefix all paths.Witted
W
281

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

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

  2. 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?

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

  4. sometimes a resource could have multiple types of parents. Resulting in multiple endpoints all returning the same resource.

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

Whorton answered 4/4, 2016 at 19:1 Comment(11)
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.Trypanosome
Would it also mean that primary key should be delivered a parameter of a query string instead of being delivered as a part of main url. /Employees?id=500, instead of /Employees/500. Wouldn't that also scale better in case you also require a primary key from a parent resource?Aleasealeatory
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. ;-)Foreshank
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.Clearly
@Clearly - 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?Sidneysidoma
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.Aldosterone
One situation nested resources seem to work for me is when we have composite keys for a resource, like repositories within GitHub, that depends on the owner's name and also the repo's name.Centrum
I mostly agree with this answer and up voted it, however, I will say that there are unique cases in application and service design where an app will not be continuously evolving and therefore, having the ability to update nested objects via the parent route(resource) will have advantages for the developer(s).Kg
"If you have /employees, what exactly does /companies/departments/employees buy you?" It buys you the ability to query the bag of employees of a specific company or the set of employees of a specific department, without otherwise embedding that data as query parameters. You could make the argument that two layers of nesting are not required to retrieve these results, but to claim that the same can be achieved with the base /employees endpoint alone (without mention of query parameters) is a pretty massive oversight in your argument.Witted
Points 2 and 5 are largely opinion-based. Your claim that query parameters are easier to incorporate into an existing API, easier to document, and more straightforward to the consumer than their endpoint counterpoints seem completely unfounded. I would argue that adding a separate endpoint to handle querying entities linked to the parent one is easier to develop than incorporating that business logic into an already busy controller. I also find that expressing resource relations in the URI path is much desirable to burying them in the query params like: ?page=X&department_id=Y&sort=Z&...Witted
@AndyDufresne if you limit the nesting to two levels, which is all that is required to enable querying all resource links in this relationship structure, you avoid this problem (and others) altogether. For example, only the endpoints /companies/{companyId}/departments/, /companies/{companyId}/employees/, and /departments/{departmentId}/employees would be supported, which simultaneously covers all possible resource links while also not enabling any inconsistent resource ID combinations, as there can only be one resource ID supplied per request (requiring multiple requests).Witted
D
195

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.

Dicarlo answered 15/10, 2014 at 17:11 Comment(9)
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.Whitman
@Whitman if you need both routes, then repeated controller code between them can be abstracted to service objects.Tide
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...Proser
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.Casias
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.Weasand
In the given example, would you expect company/{companyid}/employees to return a JSOn representation of the employees with the given company ID, or would you also expect to see a JSON representation of the company?Heyerdahl
to have the employees of all the company, I woud use company/{companyid}/departments/employees rather that company/{companyid}/employees and alse. the naming between the resources should be homogenuous between plural or singular. which means either we use demartments and employees (both with s) or department and employeeAngiology
I agree, this should be handled by making multiple requests. A good pattern would be to use a BFF (backend for frontend), i.e. a facade API, to simplify client logic.Witted
Although I generally agree with this answer, enabling the endpoint company/{companyid}/department/{departmentid}/employees introduces several problems that could be avoid entirely if the functionality was instead mapped to the truncated /department/{departmentid}/employees. 1) It forces the API client to know the companyId of the department's owning company, despite this being unnecessary. 2) It introduces ambiguity if the given departmentId is not owned by the companyId. Indeed, there is no reason to insist that the top level containment in the relationship prefix all paths.Witted
R
103

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.

Rh answered 28/9, 2015 at 10:27 Comment(16)
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.Melodiemelodion
I guess you used POST meaning PUT, and otherwise.Eleneeleni
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 https://mcmap.net/q/36058/-what-is-the-difference-between-post-and-put-in-httpRh
@Rh 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-idCantharides
@Cantharides Its not something I would personally do, but I woudn't be too shocked if it did happen. Normally I don't like mixing query strings with posted bodies. But I have done it in the past many years ago.Rh
How do you now list all employees of a given company regardless of department?Centerpiece
@Jerico its just the creation endpoint note employees are at /employees/ so a filter as normal for example /employees/?filter=company:FruitLogoRh
Why bother with POST /departments/{deparmentId}/employees. Why not send the department id as a parameter? Are you fetching the department for authorization?Heyerdahl
@Heyerdahl 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.Rh
with this approach how do you PUT or DELETE multiple departments for a given company? you need something like PUT /companies/{companyId}/departments and DELETE /companies/{companyId}/departmentsOutstrip
I didn't have that in mind when I wrote the above. Feel free to add your own answer @lp13Rh
@Rh I'm curious with this approach would it be possible to handle global employees i.e. employees which aren't tied to any company and are available to all? Do you think we should individually invoke the POST endpoints for each of them?Sanmiguel
@Sanmiguel I think that is really a different question? The use case for this is specifically when there are no global. For your use case I'd have a seperate resources. Not this approach.Rh
@Rh what if we can have global employees as well as company, department specific employees? Do you think we should center our endpoint around employees resource and then have query strings for company or department?Sanmiguel
This doesn't really work because sometimes a resource can have multiple parents that are entirely different resources. So it wouldn't be obvious which parent resource the POST request should live.Puma
@Puma Yes in that case that makes sense. However the question was specifically about a resource with one owner that has to exist beforehand.Rh
R
96

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.

Rigi answered 19/6, 2018 at 2:15 Comment(8)
The reference you give is amazing along with the point you stand out of not making complex URIs.Jerejereld
So when I want to create a team for a user, should it be POST /teams (userId in thebody) or POST /users/:id/teamsValorous
@Valorous 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.Rigi
So what's wrong with the POST /users/:id/teams. I think it is recommended in the Microsoft Document that you posted aboveValorous
@Valorous 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.Rigi
Adding to @LongNguyen comment, I think it's better to create just a /teams endpoint and to filter out using the UserID inside the controller. This approach is not only more flat and simple but less error-prone.Gamal
for requesting the orders of customer 1, why not use, /orders?customerId=1 this way, orders will always be root level resource, hence consistentWera
@kishorborate If you want an API to list all orders of customers, you can implement an API like this to filter out the customers you want by a query string. Eg: /orders?customers=1,2,3&limit=25&offset=50. If you want to list all orders of a specific customer, please adapt my tip customers/1/orders?limit=25&offset=50. Ease of use will help your partner while developing. learn.microsoft.com/en-us/azure/architecture/best-practices/…Rigi
D
20

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

Doorway answered 13/2, 2016 at 18:33 Comment(6)
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.Fleawort
This is what I said, if you also want to be able to retrieve departments, meaning if you'll use a /departments endpoint.Doorway
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.Charmian
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`Omphalos
@MatthewDaly OData also does that nicely with $expandWithhold
@Cuzox But you probably also can check the permissions on /departments as well. Why should I have both endpoints which return the same data then?Eade
P
15

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

Proser answered 5/4, 2016 at 14:49 Comment(4)
"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.Melodiemelodion
@Melodiemelodion 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.htmlProser
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 :)Melodiemelodion
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 :)Proser
A
4

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.

Accelerator answered 23/8, 2019 at 19:51 Comment(3)
You should actually provide the part of the blog which answers the question and provide links for reference.Baalman
@reoxey, the text "shallow nesting" links to the Rails documentation that explains shallow nesting. Does it not work?Accelerator
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.Redaredact
B
2

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

Badinage answered 25/9, 2021 at 1:10 Comment(1)
IMHO this is the best and shortest answer, along with @Long Nguyen citing Microsoft learning resources. You don't have to decide to use nested or non-nested design for your whole life, you have to find the sweet spot for the problem at hand. That is in most cases the flat style with some levels of nesting:). Allowing for infinite levels of nesting is stupid - that doesn't mean nesting is wrong.Whimwham

© 2022 - 2024 — McMap. All rights reserved.