Handling Foreign Key Attributes in an API with Ember Data

Last reviewed on January 18, 2020

Something I frequently see in APIs are attributes that map to foreign key database columns. For example, let's say we have the following response from a GET /animals/0 endpoint:

{
  "id": "0",
  "name": "Faith",
  "species": "cow",
  "sanctuary_id": "3"
}

Here, there is an attribute sanctuary_id that probably came from a foreign key database column named sanctuary_id.

Those new to Ember Data might create a model like this:

app/models/animal.js
import Model, { attr } from '@ember-data/model';

export default class AnimalModel extends Model {
  @attr('string') name;
  @attr('string') species;
  @attr('number') sanctuary_id;
}

The attribute sanctuary_id can then be used to look up a sanctuary record with an id of 3.

Now although this can work, what often ends up happening is the need to look up the sanctuary record by this sanctuary_id attribute, and this code gets scattered throughout the app in a few forms.

Maybe the current context already has the full list of sanctuaries. In that case, there might be code that does something like the following:

let sanctuary = sanctuaries.find(sanctuary => {
  return sanctuary.id === animal.sanctuary_id;
});

Or maybe findBy is used:

let sanctuary = sanctuaries.findBy("id", animal.sanctuary_id);

Or maybe peekRecord is used:

let sanctuary = this.store.peekRecord("sanctuary", animal.sanctuary_id);

All of these approaches get the job done, but it can be tedious to write if it occurs in multiple places and if there are multiple foreign key type of attributes. Also, this extra code makes the app unnecessarily more complex.

We can do better and eliminate this lookup by leveraging a belongsTo relationship.

Instead, we'll define our animal model like this:

app/models/animal.js
import Model, { attr, belongsTo } from '@ember-data/model';

export default class AnimalModel extends Model {
  @attr('string') name;
  @attr('string') species;
  @belongsTo('sanctuary', { async: false }) sanctuary;
}

We removed the sanctuary_id attribute and added a sanctuary belongsTo relationship.

Here I am assuming that all of the sanctuaries have already been loaded into the Ember Data store (which is often times the scenario in my experience) so I made the relationship synchronous.

So how does Ember Data map the sanctuary belongsTo relationship to the sanctuary_id property in the API response? It won't as it currently stands. If sanctuary_id was sanctuary, everything would work. If this can't be changed at the API level, we can do this mapping in the animal serializer. For example:

app/serializers/animal.js
import JSONSerializer from '@ember-data/serializer/json';

export default class AnimalSerializer extends JSONSerializer {
  attrs = {
    sanctuary: 'sanctuary_id',
  };
}

Boom! Everything now works!

Now when we need to reference the sanctuary for a given animal, we can simply do animal.sanctuary. Much simpler, right?

Assuming that all relationships follow this _id suffix convention, we could take this a step further and automatically do this by overriding keyForRelationship in the application serializer. For example:

app/serializers/application.js
import JSONSerializer from '@ember-data/serializer/json';

export default class ApplicationSerializer extends JSONSerializer {
  keyForRelationship(key, relationship, method) {
    if (relationship === 'belongsTo') {
      return `${key}_id`;
    }

    return super.keyForRelationship(...arguments);
  }
}

In my experience, foreign key type of attributes have also come up frequently in JSON:API responses, even though JSON:API relationships should be used. Although unconventional, you can use the JSONAPIAdapter with the JSONSerializer, which I wrote about in Embedded Records in Ember Data with JSON:API and follow the same approach I took in this post.


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