I was being silly to fight Ember-Data on nested URLs, I think


#1

I spent all day yesterday looking at ways to deal with my endpoint API.

/users/:user_id/sources/:source_id/items/:item_id makes conceptual sense in the API construction, which I built first. But sources, items, and users all have their own ID space, and while it’s a straightforward has-many relationship straight down the chain, there’s no real reason why I can’t do /users/:id, /sources/:id, and /items/:id instead.

I’m starting to sense that there’s a general feeling that nested URLs like that are an antipattern. If nothing else, they make item lookup a lot more complicated: if I want to look up an item, I need to know its owning source and user to construct the URL, but that’s not really necessary, since the item’s ID should uniquely identify it anyway.

The requests are pretty much identical except for looking up the list of sources tied to a user on an index route to the API (/users/2/sources would list user 2’s sources, but now it’s /sources?user_id=2). That request is actually wrong anyway, because /user/2 should have a sources attribute anyway, populated with source_id values that it owns, so my “get all the sources on a user” request goes to /users, and happens automagically when I instantiate and inflate a user. Happily this is all early prototyping work (so I don’t have to support a legacy api URL structure) and I’m writing both ends of the system, so I can change my rails router to have the flat structure instead of nesting.

This isn’t so much of a question as it is an Aha! moment that I wanted to float into the community. Does this make sense? Maybe if it does, the next poor dev that is looking to break into ember-data doesn’t lose a day coming to his/her senses.


Nested URLs for Ember Data's RestAdapter proposal
REST custom urls
Nested URLs for Ember Data's RestAdapter proposal
#2

Yeah, as a general rule of thumb, its usually a good idea to keep API endpoints as shallow as possible.


#3

I tend to try not having any resources in the query params (for instance /sources?user_id=2). I think query params should only have filters like date, min, max… that kind of things.

Then I limit my URLs and then to use this:

  • /users/2
  • /users/2/sources : all the sources resources from user 2 (which is equivalent to your sources?user_id=2)
  • /users/2/sources?prefix=blabla
  • /sources/4
  • /sources/5/items

It makes sense to me. For instance you could have a “/users/5/tweets/6/retweets”, I would rewrite it simply “/tweets/6/retweets”, as tweets n°6 is already assumed to be written by user 5.


#4

I agree with @bakura about the need to shallowly nest “index” routes.

While the request was wrong in your use-case it’s certainly more than valid in many other scenarios.

Imagine if an average user had thousands of sources. Would we really embed all sources on each user request? Better solution would probably be to embed few sources and reserve a special resource for getting the rest of them (preferably paginated).

Speaking of pagination, it makes more sense to implement the feature on the “index”-level route, rather than on the singular parent resource. E.g. /users/2/sources?page=10 is more clear than /users/2?page=10 — especially if a user resource has more nested resources.


When using shallow option for routes in Rails, we also get an “expanded” route for create action (POST /users/2/source to create a new source). In Ember context it’s probably redundant since we’re submitting user_id, but it makes integration with Rails a little bit easier, so it may be something to consider as well.


#5

@sidonath, @bakura I agree. So the question is: is there a way to do this with Ember Data right now? I can’t find any solutions yet.

The other use case is when you don’t have control over the API.


#6

The official solution to the problem of nested sub-resources would be to add links attribute to the hash of the parent resource (e.g. { "post": { "links": { "comments": "/post/1/comments" } } }.

However, I had some trouble making it work in all cases in Ember Data beta3 and it, of course, may not work for API you don’t control.

I have a fork with the modified RESTAdapter that I’m trying to keep up-to-date to allow customization of certain routes (you can find some background in this rejected pull request #1078).


#7

Sorry for picking this one up again, but I’m also dealing with a nested API. IMHO it just doesn’t matter what someone thinks about nested resources, they are not uncommon and it looks like there is no proper solution to deal with them. Even Facebook uses them as you can see here. Maybe Facebook’s developers are wrong as I am, but once again, it doesn’t matter. Should I give up on ember every time someone asks me to implement a web app for a nested API?

So, did I miss any news on this topic? Can others and I expect a proper solution? No matter what, you guys do an awesome job!


#8

Though not the definite solution, it is possible to overcome this problem by modifying the url before the xhr takes place. It all depends on the ajax library in use, but for instance in jquery one can add an ajaxSend handler like this:

$( document ).ajaxSend(
    function( event, jqxhr, settings ) { 
        // use settings hash for current values
        // and decide on the new url, then
        settings.url = 'modified url';
    });

#9

There’s more discussion happening over here: