Discover Ember

Back to All Courses

Lesson 13

Building a Node.js backend

⚠️ This tutorial uses an old version of Ember. Some things might have changed!

In this lesson, we'll finally get rid of our Fixture Adapters and instead build a real REST API with a database connection.

The most important thing to know is that Ember.js is completely backend-agnostic. You could write this part just as well in Ruby, PHP, Python or any other server language. In this lesson though we'll use Node.js since we're already familiar with the quirks of JavaScript.

Please keep in mind that this course isn't about Node.js, so we might not explain every step as elaborately as in the previous chapters. What you should know is that, just like Ember has opinions about your file structure for example, Ember Data is also very opinionated when it comes to how your REST API's URLs should be, and what the JSONthey return should look like.

Although it might seem annoying at first to structure your API responses according to a framework's particular opinions, the conventions are usually well-grounded and optimized for efficiency. If you, for some reason, in some future project, really don't like certain conventions set by the REST adapter, you can customize your adapter, or even ignore Ember Data and just use normal JSON.

Creating a November project

In order to be up and running as quickly as possible, we'll use a command line tool called November CLI to generate our Ember-friendly Node.js API (full disclosure: I am the author of the library).

Open a new terminal window and make sure this time that you are outside of your Ember folder. We want the API to be a standalone project with its own file structure.

It is good practice to keep your frontend and backend parts separate. The backend's sole purpose should be to deliver the necessary data when needed, and our Ember project is just one way to present that data (as a web app). At a later stage, we might want to use the same backend to power a mobile app for example.

First, install November CLI globally on your computer:

npm install -g november-cli

When that's done, you can create a new November project. Notice that the commands are very similar to the ones of Ember CLI.

november new chirper-api

After it is done installing all the dependencies, cd into the directory and start the server with npm start. By the way, I recommend you also install nodemon globally (if you haven't already) and run the project with the nodemon command instead at a later stage. That way, you won't have to restart the server whenever a file changes, which is quite handy.

By default, the server will run on http://localhost:9000

If you see this, then you know it works well!

Configurations

Before we go on, let's also set up our database connection. If you haven't installed MySQL, I suggest you do so with homebrew (if you have MAMP installed, you could also use that).

brew install mysql

After that, make sure you have MySQL running by running mysql.server start in a terminal window.

Next, we create our database:

# Start the MySQL command line tool:
$ mysql

# Run an SQL command to create the database:
mysql> CREATE DATABASE chirper_development;

# Quit the mysql command line:
mysql> \q

Now that the database exists, we have to make sure that that's the one we use in our November project:

// config/config.json

{
  "development": {
    "username": "root",
    "password": null,
    "database": "chirper_development", <-- The line to change

Creating the user model

Now that the configuration is done, let's create our first table which will store all of our app's users! Make sure you are in the November directory and run:

november g model user

You'll see that November creates some files for us. Let's open up the model-file so that we can define the user attributes that we want to store in our database (these will be similar to the ones we have in our Ember project):

All we need to define for now are columns for  [code]username[/code] and about_me.

Sequelize will by default add columns called created_at and updated_at which are populated and updated automatically. The only thing we need to specify is that we want our created_at-column to be called joined_at instead (since that makes more sense for a user).

// app/models/user.js

'use strict';

var ssaclAttributeRoles = require('ssacl-attribute-roles');

module.exports = function(sequelize, DataTypes) {
  var User = sequelize.define('users', {
    username: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true
    },
    about_me: DataTypes.TEXT
  }, {
    underscored: true,
    underscoredAll: true,
    createdAt: 'joined_at'
  });

  ssaclAttributeRoles(User);

  return User;
};

That's all you need to do! Now visit http://localhost:9000/users, and if you see this JSON response...

{
  "users": []
}

...then that means that your users-table was successfully created and that the REST URLs are working!

By the way, in order to easily read JSON in your browser, I recommend that you install the JSON Formatter Chrome plugin.

Creating the chirp model

The next step is to create a table for our chirps.

november g model chirp

We'll keep the model very simple here as well, but note that we're specifying a relationship to the user table (Sequelize will automatically create a foreign key). The onDelete: 'cascade'-part says that, if the user is deleted, then all of its chirps will be deleted as well.

// app/models/chirp.js

'use strict';

var ssaclAttributeRoles = require('ssacl-attribute-roles');

module.exports = function(sequelize, DataTypes) {
  var Chirp = sequelize.define('chirps', {
    text: DataTypes.TEXT(140)
  }, {
    classMethods: {
      associate: function(models) {
        models.chirp.belongsTo(models.user, {
          onDelete: 'cascade'
        });
      }
    },

    underscored: true,
    underscoredAll: true
  });

  ssaclAttributeRoles(Chirp);

  return Chirp;
};

We also need to acknowledge this relationship from the user model, so go back to the user-file and add a classMethod to it:

// app/models/user.js

// ...
  about_me: DataTypes.TEXT
}, {
  // New code:
  classMethods: {
    associate: function(models) {
      models.user.hasMany(models.chirp, {
        foreignKey: {
          as: 'user_id',
          allowNull: false
        }
      });
    }
  },

  underscored: true,
// ...

Now visit http://localhost:9000/chirps, and you should see an empty array, this time under the key chirps, which proves that the table was created successfully!

Defining the followers and followees

Finally, we need a special join-table in order to track who our users are following.

november g model follow

Now, this model is a little special since it doesn't really have any attributes of its own, it only stores two foreign keys that are both linked to the user table: the follower and the followee.

Because of this, we won't need to change anything in the follow model-file. Instead, we'll go to the user's model file, and add these two associations (right after the chirp-association):

// app/models/user.js

// ...
associate: function(models) {
  models.user.hasMany(models.chirp, {
    foreignKey: {
      as: 'user_id',
      allowNull: false
    }
  });

  // New code:
  models.user.belongsToMany(models.user, {
    as: 'followees',
    through: models.follow,
    foreignKey: 'follower_id',
    otherKey: 'followee_id'
  });

  models.user.belongsToMany(models.user, {
    as: 'followers',
    through: models.follow,
    foreignKey: 'followee_id',
    otherKey: 'follower_id'
  });
// ...

Again, visit http://localhost:9000/users and your follow-table will be created.

Verify your tables

These are the only tables that we need! To make sure that they've all been created correctly, you can launch the mysql command line tool and type in some SQL commands to verify that they're all there:

$ mysql
mysql> SHOW DATABASES;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| chirper_development    |
+------------------------+

mysql> USE chirper_development;

mysql> SHOW TABLES;
+-------------------------------+
| Tables_in_chirper_development |
+-------------------------------+
| chirps                        |
| follows                       |
| users                         |
+-------------------------------+

mysql> DESCRIBE chirps;
+------------+----------+------+-----+---------+----------------+
| Field      | Type     | Null | Key | Default | Extra          |
+------------+----------+------+-----+---------+----------------+
| id         | int(11)  | NO   | PRI | NULL    | auto_increment |
| text       | text     | YES  |     | NULL    |                |
| created_at | datetime | NO   |     | NULL    |                |
| updated_at | datetime | NO   |     | NULL    |                |
| user_id    | int(11)  | NO   | MUL | NULL    |                |
+------------+----------+------+-----+---------+----------------+

mysql> DESCRIBE follows;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| created_at  | datetime | NO   |     | NULL    |       |
| updated_at  | datetime | NO   |     | NULL    |       |
| followee_id | int(11)  | NO   | PRI | 0       |       |
| follower_id | int(11)  | NO   | PRI | 0       |       |
+-------------+----------+------+-----+---------+-------+

mysql> DESCRIBE users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| username   | varchar(255) | NO   |     | NULL    |                |
| about_me   | text         | YES  |     | NULL    |                |
| joined_at  | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

If you get the same results, you're all set and ready to convert the data fixtures in your Ember app to use the Node.js backend!

If you, on the other hand, get results that differ from the ones above, delete the faulty table and please go through this lesson again. You probably made a mistake somewhere, and it's important that you have the exact same table structure.

The upcoming chapters will rely heavily on a working database, so make sure you get it right!