Question

I'm working on the frontend of a single page application and I have to list a number of students. Each student is attached to a certain user_id This is what the API returns for all user roles (superadmin, admin, professor, student) when I perform GET /students:

{
  address: null
  class_id: 184
  class_name: "I B"
  date_of_birth: null
  first_name: "first"
  gender: null
  grade: 1
  id: 192
  last_name: "last"
  nationality: null
  place_of_birth: null
  ranking_by_class: 0
  ranking_by_school: 0
  registration_number: null
  user_id: 238
}

I'm working on the superadmin role at the moment where I'm in need of extra data from each student (subscription_type), which is only available on GET /users/:id

So when I'm listing 20-30 students on a page via GET /students/, in order to get the subscription_type, I'll also need to do 20-30 extra requests, one for each student.

I talked to the API guy about this and I was told that including the extra data into students "is not the RESTful way of doing it", "it will slow down the response times even more" and that "30 extra requests weigh less than a big chunk".

I don't know anything about working on an API, so I can't really have a say, but am I crazy to think that 30 requests on a page load is ludicrous?

So what next? Do I go ahead and perform the extra requests? Should he separate the responses for each user role and include just the things I need for each role? What is the correct way of handling this?

Was it helpful?

Solution

While strictly speaking the API guy is correct, following the RESTful gospel too strictly can lead to inefficiencies like the 1+N problem created by the purist implementation. A key concept in RESTful design is that resources do not have to map directly to domain objects. When designing resources one must look at the usage scenarios. In the super admin scenario, it sounds like when the client requests a collection of students and it also typically or always needs data from users, specifically subscription_type. The object model is clearly normalized as it should be, but that does not say that the resources must be, or must always be.

There are a couple of different patterns I have used to make similar scenarios more efficient. Which (if either) apply depends on how the resources are consumed by clients.

Composite Resource

This is the combination of all or part two or more domain objects (e.g. student and user) into a single resource.

Since all students are presumably also users, you could include all or part of the user data in the student resource as appropriate.

GET /students

{
  address: null
  class_id: 184
  class_name: "I B"
  date_of_birth: null
  first_name: "first"
  gender: null
  grade: 1
  id: 192
  last_name: "last"
  nationality: null
  place_of_birth: null
  ranking_by_class: 0
  ranking_by_school: 0
  registration_number: null
  user_id: 238
  subscription_type: "foo"
}

Related Resources

(Similar to another response) This is a technique where the client can indicate that it wants a related resource included in the response. This is particularly useful in "has a" type relationship in the domain model. It allows the client to essentially lazy load or eager load the resource.

GET /students

{
  address: null
  class_id: 184
  class_name: "I B"
  date_of_birth: null
  first_name: "first"
  gender: null
  grade: 1
  id: 192
  last_name: "last"
  nationality: null
  place_of_birth: null
  ranking_by_class: 0
  ranking_by_school: 0
  registration_number: null
  user_id: 238
}

GET /students?include_user=true

{
  address: null
  class_id: 184
  class_name: "I B"
  date_of_birth: null
  first_name: "first"
  gender: null
  grade: 1
  id: 192
  last_name: "last"
  nationality: null
  place_of_birth: null
  ranking_by_class: 0
  ranking_by_school: 0
  registration_number: null
  user_id: 238
  user:
  {
   id: 238
   subscription_type: "foo"
   ...
  }
}

OTHER TIPS

You have two issues here, that shouldn't be tied together - One is the data returned for each user for the index call (/students), and the other is the authorization process that should determine which data a certain user can be exposed to.

Regarding the data issue - I don't think there's a specific rule to follow. It totally depends on your app's requirements. If the subscription_type field is usually unnecessary, you may consider passing a query parameter ('include_extra_data=1' for example) to indicate that you need it on the specific request, where by default it won't be returned.

Regarding the authorization issue - This should be totally disconnected from the data your asking on your request. The server should be able to determine which data is visible to the user according to the user id. (you can check out the CanCan gem][1] as a possible solution).

So, combining the two issues, any type of user should be able to make the '/students' request with or without the 'include_extra_data' flag. If the server find the user unauthorized to see the extra data, you can choose one of 2 options - Either return only the data the user is allowed to see, or return a '401 unauthorized' response.

Both ways are valid, and affect the way you deal with the response on your client app. (I, personally, prefer the later, as it's more descriptive for the client).

Hope this helps :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top