Andy Crum

Getting Started with Ember Data

Soon after you start creating an application in Ember.js, you’ll run into the need for data persistence. Most web applications accomplish data persistence by storing data on the server. Your application is probably no exception. That’s where Ember Data comes in. Note: if you aren’t familiar with Ember.js yet, it might help you to read my introduction to Ember.js first, then come back to this post.

Ember Data’s purpose

The Ember guides describe Ember Data’s purpose here:

Ember Data is a library that integrates tightly with Ember.js to make it easy to retrieve records from a server, cache them for performance, save updates back to the server, and create new records on the client.

Here’s how it’s described on the GitHub project page:

Ember Data is a library for robustly managing model data in your Ember.js applications.

The core team also described it in a blog post like this:

We think of Ember Data as a framework for managing your models and relationships.

In a nutshell, Ember Data allows you to define models inside your application, define relationships between those models, and load records from the server that are instances of those models. It also saves your changes back to the server when the records are updated.

You don’t have to use Ember Data with Ember – many people have written custom libraries for this purpose. It’s possible to “roll your own” using jQuery.ajax and Ember.Objects (or something similar), but that can quickly turn into the kind of spaghetti code we’re trying to avoid by using a framework in the first place. Let’s go over some of the benefits and drawbacks to using Ember Data.

Benefits

The benefits to using Ember Data are very similar to the benefits of using Ember.js itself. It has a clear syntax, is well-maintained, and is backed by a solid core team that thinks hard about architecture and how their choices affect developers. The syntax for creating models and relationships could not be any clearer, and the fact that it is so well integrated with Ember is a big benefit.

Ember Data is the default data library for Ember, so it’s hard to argue that there’s a better choice.

Drawbacks

One drawback to using Ember Data (at least right now) is that it has not officially hit version 1.0 yet. This means the API is subject to change, and the core team recently said that a couple API changes will happen before Ember Data hits 1.0, so some of the code you write will have to be changed if you want to update to a post-1.0 version.

How does it work?

There are several concepts that make up Ember Data’s core functionality: models/records, relationships, the store, and adapters/serializers.

Models/Records

I’m using the word models to define the representation of an object in your application, and the word records to define instances of those models.

Ember Data is where you define your application’s models, and so it is also in charge of storing all the instances (records) of those models.

Relationships

Relationships are the connections between models. These are declared when you define the models, so it’s not a separate definition per se, but it’s such an important way of how Ember Data works that it warrants a mention.

Ember Data has two types of relationships: DS.hasMany and DS.belongsTo.

Store

The store is where all of your records are kept. It’s the local data store that keeps records in memory while you’re using your application. When you include Ember Data, the store is injected into every route and controller in your application, making it easily accessible from routes and controllers by typing this.store.

Adapters/Serializers

Adapters and serializers are what Ember Data uses to talk to your server. Adapters specify the endpoints that Ember Data should hit to create/retrieve/change records, and serializers specify what data is sent (and the format of that data). They translate between “Ember-speak” and “server-speak”.

Ember Data comes with three built-in adapters:

  1. RESTAdapter – The default adapter. It expects data to be returned from the certain endpoints on the server in a pretty specific JSON format (we’ll go into that in a bit).
  2. ActiveModelAdapter – This adapter expects data in the format returned by the ActiveModel::Serializers gem for Rails.
  3. FixtureAdapter – This adapter expects data in the same format as the RESTAdapter, but not from the server. This is just for creating local, non-server fixture data, which can be very useful for prototyping and testing.

All adapters and serializers can be customized simply by creating a subclass of them and changing properties and methods to your liking. You can also use different adapters and serializers for different models. Yes, things are getting pretty crazy up in here. When Ember Data is trying to determine how it should sync the App.Post model, it will look first for an adapter named App.PostAdapter. If that doesn’t exist, it will look for App.ApplicationAdapter, and lastly will default to the RESTAdapter. The same pattern for extending applies to serializers.

An Ember Data example

For me, these were the two hardest things about getting started with Ember Data:

  1. Figuring out what code was needed to set up models and retrieve records.
  2. Understanding how the response from the server should be formatted.

There’s much more documentation out now (such as this page on the official guides), but I still think it’s helpful to see an example. We’ll go with the time-tested blog example, with models for Post and Author.

To set up our models, all we need are a couple model declarations using DS.Model.extend. Here they are:

App.Post = DS.Model.extend({
  title: DS.attr('string'),
  body: DS.attr('string'),
  author: DS.belongsTo('author')
});

App.Author = DS.Model.extend({
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  posts: DS.hasMany('post')
});

Retrieving records

We now have our models. So how do we retrieve records, and what does the server need to return so that Ember Data recognizes them? On our blog, we probably just want the index page to list our posts. So our IndexRoute would look like this:

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('post');
  }
});

Remember how I said before that the store was accessible in every route and controller? There it is! The store has many more methods that are helpful, but for now a simple call to find will call our server to retrieve the posts. By default, it will call the endpoint /posts, unless you’ve specified a different host or namespace when setting up your adapter.

An aside: specifying a different host and namespace

Many applications won’t have server calls accessible at [host]/posts. Often your endpoints will be on subdomains or a different path (such as one including an API version number). Maybe you even need to send custom headers along, such as an API key or user id. Ember Data’s RESTAdapter deals with this very well. When creating the adapter, you can specify properties to deal with all of these problems:

App.ApplicationAdapter = DS.RESTAdapter.extend({
  host: 'http://api.example.com',
  namespace: 'v2',
  headers: {
    'COOL_HEADER': '1234',
    'COOLEST_HEADER': '5678'
  }
});

This would send requests for posts to the endpoint http://api.example.com/v2/posts, and would send along the two specified headers with every request. Have different endpoint paths for different models, you say? No problem. As I mentioned before, adapters can be declared on a per-model basis. For example, if you need your authors to be retrieved from a different API version, you can simply do this:

App.AuthorsAdapter = App.ApplicationAdapter.extend({
  namespace: 'v1'
});

Notice that I extended App.ApplicationAdapter there, instead of DS.RESTAdapter. You could extend DS.RESTAdapter instead, but I wanted to make sure any change in the other properties of App.ApplicationAdapter would also be present in App.AuthorsAdapter.

Formatting the server response

Now that we’re making the correct calls to the server, we should know how to format the response so that Ember Data understands it and loads it in as records.

If you’re using the RESTAdapter, here’s an example of what Ember Data expects as a response to a GET /posts call:

{
  "posts": [
    {
      "id": 1,
      "title": "An Awesome Blog Post",
      "body": "This is the best blog post ever.",
      "author": 1
    }
  ]
}

Since author points to an author id, if Ember Data has already loaded the authors into the store, it’ll just load the correct author from the data store (if it’s required in the template). If the author with id=1 has not yet been loaded, Ember Data will make a call to GET /authors/1 to retrieve the author information if needed. As an alternative, you can include authors as a sibling to posts in your original response (with the related author record), and Ember Data will know what to do with that, which will avoid making another server call. Here’s what that would look like:

{
  "posts": [
    {
      "id": 1,
      "title": "An Awesome Blog Post",
      "body": "This is the best blog post ever.",
      "author": 1
    }
  ],
  "authors": [
    {
      "id": 1,
      "firstName": "Andy",
      "lastName": "Crum",
      "posts": [ 1 ]
    }
  ]
}

You can also include a links hash inside a post object, which has the path that should be called to retrieve the correct associated author object (or objects, if needed). This is helpful if, for example, you had multiple authors per post and wanted to avoid making a call for each author. Let’s say you have multiple authors and want to use that method. Here’s an example of how the response from GET /posts would look:

{
  "posts": [
    {
      "id": 1,
      "title": "An Awesome Blog Post",
      "body": "This is the best blog post ever.",
      "links": {
        "authors": "/authors?post_id=1"
      }
    }
  ]
}

Ember Data Model Maker

When trying to figure out Ember Data, I could never really find any one thing that made it clear what the server response should be. Hopefully my explanation here was clear, but I also made a tiny app called Ember Data Model Maker that might be useful to you. Feel free to experiment with it – hopefully it will give you some extra insight into Ember Data. The code is also hosted on GitHub, so check it out – it’s built with Ember.js and Ember Data (so meta).

Wrapping up

If you want to learn more about Ember Data, check out the section on models on the official guides, as well as the official API documentation.

Ember Data can be a bit tough to get started with, but hopefully this post has been able to clear up some confusion. While technically it’s still in beta, it’s remarkably mature for being at that stage. If you still have doubts, you owe it to yourself to watch the official Ember blog and Ember Data repository for updates, and give it a shot when it hits 1.0. If you think you’re ready, grab the newest beta build and try it out!

Do you have questions or comments about something I wrote here? Let me know at @andy_crum or andy@andycrum.com!