3

When designing a REST API, how deep should the resources levels go?

For example; If I have the following database tables:

  • Companies: ID, Name
  • Employees: ID, Name, CompanyID
  • Tasks: ID, Name, EmployeeID

When exposing these through a REST API, it might look something like this (id is optional):

  • Companies: GET /api/companies/{id}
  • Employees: GET /api/employees/{id}
  • Tasks: GET /api/tasks/{id}

Lets say I want to retrieve all the employees working at company 1. I could do something like this: GET /api/companies/1/employees, right?

Performing a GET on /api/employees would be useless, because you don't want to return all the employees stored in the database. Should the /api/employees resource only be used in this case for retrieving individual data about a specific employee?

What if I want to get the tasks associated with an employee? Should the URL be:

GET /api/companies/1/employees/123/tasks

or

GET /api/employees/123/tasks.

If you don't want clients to have access to certain resources, without knowing its parent, how do you design your resources?

If I'm not being clear enough, or have to many questions, please let me know.

  • 2
    As far as I know, there's no limit to the number of sublevels; it's all about how you program the routing engine you're using to manage the URLs. So design an API that makes sense to you. The examples you've provided so far seem sensible. – Robert Harvey Nov 18 '13 at 18:00
  • What if a freelancer works at two companies? GET /api/companies/1/employees/123/tasks should only return tasks from a person at that company. GET /api/employees/123/tasks gets all tasks from an employee. so these resource urls can both exists but differ from deliverable. – Timmetje Sep 29 '15 at 11:51
5

Yes,

GET /api/companies/1/employees

would be the way to get the employees of a company.

What exactly your employee resource offers would depend on some details of your application. For example you could have logins for admins that are directly related to a specific company. It this case it would be a possible shortcut the employees resource and directly go to an employee or list of employees. But in normal cases /api/employees would only work in combination with companies.

Very interesting question would be the nesting levels. This is one of the questions some people can discuss for hours. Ruby on Rails for example has now an option for "shallow" routes.

While there is no hard technical limit to the number of levels (max length of an URL maybe) I think two levels should be enough for most cases and deeper nesting should be avoided if possible since doing so will make your code better structured and maintainable.

Your example is quite typical and it is easy to show why you would not really need a third level for the tasks in most scenarios. Your tasks are directly related to an employee. So the entity resource somehow already carries the information about the company. (Selecting an employee automatically selects the company). So providing the company in the URL again somehow repeats information.

GET /api/employees/123/tasks

has all the information necessary to get to your data. So why make it more complex?

I'm not sure if I fully understand your last question.

Restricting access in this context is rather easy. Say you have a client who has only access to resources of the company with id 123. Then all you have to do is to start your controller code by assigning some "hard limits". If the logged in user is not allowed to access anything but one company, then create an obkect for that company and from there on use only this object for any firther access.

Assume we have a Rails projects. For this we have a model for each resource you mention. In addition we have an authentication system in place that gives us the currently logged in user. In this case it would be as easy as this to return something like the list of employees:

current_user.company.employees

Notice that it does not even try to use any company id used in the URL (which in case of Rails would be stored in params[:company_id]).

Try to get the tasks of a specific employee with

GET /api/companies/123/employees/456/tasks

current_user.company.employees.find(params[employee_id]).tasks

Notice again that this is going the "full path" instead of simply using

Employee.find(params[employee_id]).tasks

The first form will create a SQL query with a lot of LEFT JOINS that starts from user, goes through company, employees and tasks. And important in this case: It will fail if a single relation is missing.

Even if not in context of a login and permissions you can still use this to enforce that the user has a certain knowledge about the data. Though in this case you would maybe have to use deeper nested routes. Same example without login but the user making the request must know the correct company id. In this case:

GET /api/companies/123/employees/456/tasks

Company.find(params[:company_id]).employees.find(params[employee_id]).tasks

Again go the full way. If your framework does not have this kind of object relations simply nake sure that your SQL requests use JOINS in a similar fashion.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.