Handling Nested Resources and Relationship Links in Ember Data

Last reviewed on October 24, 2016

Many APIs use nested resource paths. That is, URL paths that contain a hierarchy of resource types. For example, a nested resource path might look something like: /users/5/pets, where there is a collection of pet resources under a user resource. How do you handle that in Ember Data?

Let's say we have a user model with asynchronous belongsTo and hasMany relationships:

app/models/user.js
export default DS.Model.extend({
  first: DS.attr('string'),
  last: DS.attr('string'),
  pets: DS.hasMany('pet', { async: true }),
  company: DS.belongsTo('company', { async: true }),
});

Ember Data supports relationship links. What that means is that we can have a property called links on individual resource objects, which is an object that contains URLs that point to related data.

With the DS.RESTSerializer format, relationship links for a user resource object would look like:

{
  "users": {
    "id": 1,
    "first": "David",
    "last": "Tang",
    "links": {
      "company": "/api/v1/users/1/company",
      "pets": "/api/v1/users/1/pets"
    }
  }
}

With the DS.JSONSerializer format, relationship links for a user resource object would look like:

{
  "id": 1,
  "first": "David",
  "last": "Tang",
  "links": {
    "company": "/api/v1/users/1/company",
    "pets": "/api/v1/users/1/pets"
  }
}

With JSON:API, relationship links for a user resource object would look like:

{
  "data": {
    "id": "1",
    "type": "users",
    "attributes": {
      "first": "David",
      "last": "Tang"
    },
    "relationships": {
      "company": {
        "links": {
          "related": "/api/v1/users/1/company"
        }
      },
      "pets": {
        "links": {
          "related": "/api/v1/users/1/pets"
        }
      }
    }
  }
}

Not sure about the differences between the different serializer formats? Check out the post Which Ember Data Serializer Should I Use?

If we made a request to /api/v1/users, each user resource object in the response can have a links property. When you access user.pets or user.company, Ember Data will trigger a fetch using these URLs defined in links.

As noted in the API documentation:

The format of your links value will influence the final request URL via the urlPrefix method: Links beginning with //, http://, https://, will be used as is, with no further manipulation. Links beginning with a single / will have the current adapter's host value prepended to it. Links with no beginning / will have a parentURL prepended to it, via the current adapter's buildURL.

What if your API doesn't return a links property, and this is how your related data needs to be accessed? I have found this to be a pretty common scenario.

To handle this, we can add these relationship links manually in our serializer during the normalization process. Specifically, we can override one of the normalization methods in the serializer like normalize(), normalizeResponse(), normalizeFindAllResponse(), etc, and create a links property for each individual resource:

For example, if you are calling store.findAll('user'), you can override normalizeFindAllResponse().

app/serializers/user.js
export default DS.RESTSerializer.extend({
  normalizeFindAllResponse(store, primaryModelClass, payload, id, requestType) {
    payload.users.forEach(user => {
      user.links = {
        pets: `/api/v1/users/${user.id}/pets`,
        company: `/api/v1/users/${user.id}/company`,
      };
    });

    return this._super(...arguments);
  },
});

Here I have created a model-specific serializer to add links to each user resource. You could probably make this a little more dynamic and use it across the board in an application serializer. I'll leave that to you.


Pro Ember Data cover
Pro Ember Data available now!

Are you struggling with Ember Data?

In my new book Pro Ember Data, you will learn how to work with Ember Data efficiently, from APIs, adapters, and serializers to polymorphic relationships, using your existing JavaScript and Ember knowledge. This book will teach you how to adapt Ember Data to fit your custom API.

What You'll Learn

  • Review the differences between normalization and serialization
  • Understand how the built-in adapters and serializers in Ember Data
  • Understand how the built-in adapters and serializers in Ember Data work
  • Customize adapters and serializers to consume any API and write them from scratch
  • Handle API errors in Ember Data
  • Work with the Reddit API using Ember Data
  • Learn how to use polymorphic relationships
Check out Pro Ember Data on Amazon using my affiliate link
You can also find Pro Ember Data on Apress.

I've been enjoying @iamdtang's Pro Ember Data book. It's to the point and has lots of great examples. My favorite part was about error handling. This book is perfect after you've exhausted the official guides.

Ilya Radchenko

Product Developer at Applied Geographics

Ilya Radchenko

Pro Ember Data is such an approachable book @iamdtang! Good job. Loving the book so far! Thank you!

Lenora Porter

Software Engineer at Heroku

Lenora Porter

Great to see all those topics in which I struggled when I started working on Ember. Nice job, will check it out :)

AbulAsar S

Fullstack Developer

AbulAsar S

I haven't read David's new book, but his previous Ember Data book helped me a lot when I was starting out with Ember, so I am sure this book is a must if you're working with Ember Data!

Kenneth Larsen

Ember Learning Core Team member

Kenneth Larsen

I bought Ember Data in the Wild back in 2016 and found it really helpful. Looking forward to reading this.

@EmberLinks

Ember related news by Chris Masters

@EmberLinks