The simplest possible Ember Data CRUD Tutorial

For Ember versions 2.0+. Assumes previous experience building a back end and database. Last reviewed at version 2.14. Special thanks to J G Lopez and Braden Lawrence for editing and feedback :D

In this Ember Data tutorial, we will create just four files, plus add some code to one that already exists. That’s it.

When I was a developer-in-training, all my projects began with setting up CRUD — Create, Read, Update, Destroy. I try out the most minimal CRUD before building out the “real” app and its features. In contrast, most Ember tutorials weave in a lot of information about Ember architecture, and that’s not a bad thing, but if you’re having trouble understanding Ember Data and the connection between your app and API, a different approach may help.

So, how can you get your Ember app to talk to your API? We’ll walk through step by step. Here’s a link to the finished demo app that you can run locally.

Is Ember Data worth it? What does it do?

Yes. Ember Data is the link between your back end and Ember. It’s like a little mini database in the browser: it disappears when you refresh, but you can search, edit, and load things from it very quickly. You get a lot of helpful features out of the box that keep your user interface in sync with your API/Database. If you create a new record, BOOM it shows up in front of the user and a POST request goes out to your API. No JQuery, no refreshing, no ajax, no form actions. If you delete the record, it disappears immediately. Your visuals stay up to date with your back end/database, and you don’t have to manage any of that.

But it also means you have some new things to learn. If plain old JavaScript + JQuery is like riding a bike, Ember Data is like riding a horse. A horse moves on its own with just a little input from you, but you need to know how to communicate with it. Also, horse rhymes with Open Source, and I love both of those things. So there’s that.

Dear Tom Dale and Yehuda Katz, if you are reading this, and I hope you are, plz use this in your slide decks. I think it’s a compelling way to sell Ember’s strengths. Alt: “Use Ember Data… because nobody rides a bike into battle.” Original photo by Andrew Yardley on Unsplash

What these files are used for

Here’s a high level overview of the files we’ll be changing and what they do. We’ll walk through creating them using the Ember CLI and add some code to each.

Ember Data CRUD filetree, showing the roles of the application adapter, controller, route, and template, plus our record model.

Specifying the URL of your API in the adapter

Chances are, when you are developing your Ember app locally, you’ll be running your back end locally too. That back end will be serving from a localhost port. For example, my Node back end runs at http://localhost:3000 but yours might have a different port number than 3000. Wherever your API is, we’ll put that URL in the adapter.

Sidenote: If you don’t have a back end set up yet but you want to follow along, you’ll need to have a “mock server” that catches your network requests so you can inspect them. It’s easy to do. Just run ember g http-mock boardgames and paste this code in. Then do npm install --save-dev body-parser. Instead of “host” in the example below, specify namespace: 'api' . All the examples should work for you by the time you get to the end.

The Ember Data adapter handles where requests go to and how they’re formatted. Create one with

ember g adapter application

Then navigate to adapters/application.js, where you’ll specify the url to your back end as the host . Later when you deploy your front end, you’ll need to change this URL to wherever your deployed back end server is. Don’t forget!

// adapters/application.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
host: 'http://localhost:3000'
})

An app can have many adapters, butapplication.js is special. It is the default adapter that will handle all API requests until you make more adapters (if you even need them).

By default, the Ember CLI created a JSONAPI adapter for you. Adapters come in different flavors. See that “export default” line above? If your back end is not using JSONAPI, you’ll want to use DS.RESTAdapter . What’s the difference? JSONAPI is a standardized format for making API requests. REST gives you more freedom, but you also have to write a lot more code.

JSONAPI post request example:

{data: {attributes: {title: "Settlers of Catan"}, type: "boardgames"}}

REST post request example:

{boardgame: {title: "Dominion"}}

The docs have lots of examples of requests/responses for both kinds of adapters.

Define your model

The model tells Ember Data what kinds of information to expect to be CRUD-ing. It describes one kind of resource. We’ll keep it super duper simple. Run this to create a model file:

ember g model boardgame

Now, when we make API requests about a boardgame, it will automatically be made to http://your-adapter-host/boardgames.

The model needs at least one property. My boardgames have a title:

// models/boardgame.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string')
});

A small disclaimer

We’re about to add some code to a lot of files called application. Normally this code wouldn’t go there, because “application” files often affect the whole app. Normally, the following code would go into routes and controllers that have other names. In order to avoid explaining the entirety of Ember app architecture, we’ll work with the smallest number of files that we can, and we’re going to hard code some things. This is a starting place.

Setting up the controller and route template

We’ll work with 3 files —a route template, a route JavaScript file, and a controller. I’m going to give you all the Controller and Template code upfront. But don’t expect it all to work just yet!

First, let’s make the controller:

ember g controller application

Our controller is the home for Creating, Updating, and Destroying functions. Add this to your new file at controllers/application.js:

You’ll see that all of the CRUD functions are in an object called actions. Actions are special functions that we can call from templates. Actions are the Ember equivalent of event handlers. By default, that watch for click events, so clicking on a button that has an action in it will trigger a function of the same name in our controller.

Next, let’s create our route JavaScript. We’re not going to use it yet, but we’re generating it now so we don’t risk overwriting our template later on:

ember g route application

Say “no” to overwriting… just to be safe in case you’re doing this tutorial out of order. P.S. don’t forget to commit as you work.

Now we’ll add some HTML/handlebars to the route template. Put this in templates/application.hbs to make some inputs and buttons:

Take a look at the buttons. See the “action” listed on each? Those match up to the functions in our controller. Now check out {{input value=someValue}} — those are special Ember text inputs that will create a form field. See the value=someVariable on the inputs? Those value variables are used inside the CRUD functions in the controller. this.get('someVariable`) grabs the user’s text entries from the form and makes them available in our JavaScript file.

Creating

Run your app locally with ember serve and visit it at http://localhost:4200 . Type in the field next to “Create” and click the button. createRecord adds the information to the local Ember Data Store. .save() initiates a POST request to the back end to persist the record. Your POST request might fail. We’ll fix it.

First, let’s see if our new record made it into the Ember Data Store. The data store is like a temporary database that lives in the browser. All of its contents disappear when you refresh, but until that point, you can play around with the new records. Open up the Ember Inspector in your Chrome developer console. The Ember Inspector is a plugin you can get from the Chrome Web Store if you don’t have it already. Click on the data tab, and you should see some records!

Our newly created board game records made it into the Ember Data Store!

Now let’s see what happened on the back end. Open the Chrome inspector and look at the network tab. Click on your most recent network request (it’s probably red) and you can inspect where the POST request was made to. At the very top, you will see “Request URL.” That ought to be the URL of your back end. If you already built your back end, you should have an endpoint defined that handles a POST request to /boardgames. Scroll down to the bottom to see what information was sent, aka the request body or payload:

Outgoing request using the JSONAPI adapter

If your POST request failed and this surprises you, a few different things could be wrong:

  • You are making requests to the wrong URL. Look at it in the Chrome inspector. Should it be singular instead? Dasherized? Time to read The Docs for JSONAPIAdapter or RESTAdapter to customize it.
  • You don’t have a /boardgames POST endpoint set up in the back end
  • Your parsing is failing on the back end (just console log everything, and don’t forget to transform your response into JSON if your back end framework makes you do this manually)
  • The names of the attributes on the back end don’t match what Ember is sending. For example, should they be snake_case instead of dasherized? Time to research custom serializers… or make them match.

If/when your POST request succeeds, you’ll see some JSON in the “Response” tab in the Network section of the console. But there may still be errors in the browser console. Take a look. Ember Data expects that a POST request has a response that contains the freshly created object, including an ID. The docs have lots of examples of correctly formatted responses for both JSONAPI and REST. Here’s a simple JSONAPI example response:

{“data”:{“id”:4,”attributes”:{“title”:”Sushi Go”},”type”:”boardgame”}}

You’ll know that you’re 100% successful at connecting Ember Data to your API when there are no errors in the console after a POST, and when you use the Ember Inspector and select Data, you can see your board game, and it has an ID. Remember, the back end is responsible for assigning IDs. They will be filled in using the response from the POST request. Also keep in mind that anything you create will disappear from the Ember Inspector/Data Store when you refresh. It’s still there in your database, but we aren’t loading it into the front end when the app starts up. We’ll do that next.

Reading (GET all)

Now that our store has stuff in it, let’s see it in the template. The template we worked with earlier, templates/application.hbs referenced something called a model . I’m going to try and explain the 3 main uses of the word “model” in Ember:

  1. The model in models/boardgame.js is the definition of what a board game should look like
  2. The model function we’re about to add to routes/application.js is a special function that triggers GET requests and helps display the results. This is often referred to as the “model hook.” Hooks are functions that are automatically called when the user views the template for the route… kind of like document.ready gets called in JQuery when the page has loaded. Repeat after me… model hooks belong in routes.
  3. The model referenced in the template displays whatever was returned from the model hook in the JavaScript file.

Add this to your routes/application.js. The home of Reading/GET functions is the model hook in a route.

import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.findAll('boardgame')
},
});

When you call findAll , you’re asking Ember Data to make a GET request to /boardgames and to load the results into the Ember Data Store. Basically, we’re looking up all the records that you can see in the Data tab of the Ember Inspector. By returning the results, we are making them available in our template as model .

findAll fetches a collection/array of Ember Data records. If you console log the model or the results of findAll, you get gibberish because we’re working with records that are alive (like horses). They are not plain old JavaScript objects (the bike). I’ll tell you how to work with them.

Model is an array (or collection) of board game records, so in order to work with an array, we need to iterate over it. Ember has a special helper for the template called each . Inside of the each helper, we’re working with a single board game record, and so we can display the titles of each game.

 <ul>
{{#each model as |game|}}
<li>{{game.title}}, id {{game.id}}</li>
{{/each}}
</ul>

Here’s where Ember Data gets cool. As you CRUD records, the model gets updated too, in real time, without you needing to do anything. Try it!

Of course, in order for this function to work, you need a back end server endpoint that returns an array of records. Take a look at the Network tab and your server console to see what’s going on if nothing is showing up. Refresh the page. Is everything still there? Awesome.

Destroy

My favorite. Here’s the code that you should already have in your controller:

destroyBoardGame() { 
let destroyId = this.get('destroyId')
let game = this.get('model').findBy('id', destroyId)
game.destroyRecord()
}

You might remember that one of the inputs on our template had a value of destroyID . Here we use this.get('destroyId') to look up what the user had entered into the form. Then we look at the model (a collection of board games) and find the game with the id the user entered. destroyRecord deletes the record from the Ember Data store AND saves the change by doing a DELETE request. If you use deleteRecord instead, there won’t be a request to the API.

You’ll know it worked when you refresh the page and anything you deleted is gone from the Read All list.

Updating

Editing/updating is a little weird. In a “real app,” you would have a special route that only displays information for one record in its model hook. But since we don’t have such a route, we’ll look up the record on our collection of models using findBy , make some changes, and save them. Again, console logging Ember Data records won’t give you anything useful (to a noob) in the console.

I’m hard-coding an ID in here purely for demonstration purposes. You wouldn’t ever hard code an id into API/Ember Data requests in a real app. If you destroyed record #1, you’ll need to edit the function to point to a different id than 1.

updateBoardGame() {
let updatedTitle = this.get('updatedTitle')
let game = this.get('model').findBy('id', '1')
game.set('title', updatedTitle)
game.save();
},

set() changes the record’s title locally in Ember Data. Calling save on a record that already exists triggers a PATCH request. If you’re successful, you should be able to refresh the page and see that your title is still changed. If it goes back to the old title, that means you need to check the browser console for adapter errors or the Network tab to check the format of your request and API response.

More Reading (GET one record)

I saved this for last because a GET request for one record doesn’t really belong in the controller. I also cheated in this example and hard coded the ID for the GET request. It’s for demonstration purposes. Here’s an excerpt from the code you already have in controllers/application.js :

readBoardGame() { 
this.store.findRecord('boardgame', 1)
.then((game) => {
alert(game.get('title') + ' ' + game.get('id'))
})
},

This findRecord places a GET request to boardgames/1 . Depending on your back end framework, the id may be available as params, which you can use to look up the record in the database. Your API should respond with a single record.

Try clicking the button with the readBoardGameaction. It ought to trigger a pop up that shows you the title of the board game with id 1. We are doing three Bad Things here…

First, you shouldn’t use alert in a real app. It’s annoying and ugly. But whatevs.

Second, don’t hard code IDs.

Finally, and most importantly, in a real app, I would usually be doing findRecord inside of a model hook, so that I can use all the awesome automatic updating powers that models have. I’d have some dynamic segments, I’d have parent and child routes, there would be some interesting things going on in the router and child route models, we’d have A LOT more files! But you asked for the simplest possible CRUD, and so I have given it to you.

I hope you’re happy.

…no really, I do :P

Now go read The Guides and the API Docs for Ember Data. Good luck!

P.S. If you have feedback about how to make this tutorial better, I’d love to hear from you. @jwweber on Twitter and jenweber on Ember Discord.