JavaScript
Article

Deploy Your Own REST API in 30 Mins Using mLab and Heroku

By Chris Chang

This article was first published on the Heroku Dev Center

The MEAN stack is a popular web development stack made up of MongoDB, Express, AngularJS, and Node.js. MEAN has gained popularity because it allows developers to program in JavaScript on both the client and the server. The MEAN stack enables a perfect harmony of JavaScript Object Notation (JSON) development: MongoDB stores data in a JSON-like format, Express and Node.js facilitate easy JSON query creation, and AngularJS allows the client to seamlessly send and receive JSON documents.

MEAN is generally used to create browser-based web applications because AngularJS (client-side) and Express (server-side) are both frameworks for web apps. Another compelling use case for MEAN is the development of RESTful API servers. Creating RESTful API servers has become an increasingly important and common development task, as applications increasingly need to gracefully support a variety of end-user devices, such as mobile phones and tablets. This tutorial will demonstrate how to use the MEAN stack to rapidly create a RESTful API server.

AngularJS, a client-side framework, is not a necessary component for creating an API server. You could also write an Android or iOS application that runs on top of the REST API. We include AngularJS in this tutorial to demonstrate how it allows us to quickly create a web application that runs on top of the API server.

The application we will develop in this tutorial is a basic contact management application that supports standard CRUD (Create, Read, Update, Delete) operations. First, we’ll create a RESTful API server to act as an interface for querying and persisting data in a MongoDB database. Then, we’ll leverage the API server to build an Angular-based web application that provides an interface for end users. Finally, we will deploy our app to Heroku.

So that we can focus on illustrating the fundamental structure of a MEAN application, we will deliberately omit common functionality such as authentication, access control, and robust data validation.

Prerequisites

To deploy the app to Heroku, you’ll need a Heroku account. If you have never deployed a Node.js application to Heroku before, we recommend going through the Getting Started with Node.js on Heroku tutorial before you begin.

Also, ensure that you have the following installed on your local machine:

Source Code Structure

The source code for this project is available on GitHub at https://github.com/sitepoint-editors/mean-contactlist. The repository contains:

  • package.json — a configuration file that contains metadata about your application. When this file is present in the root directory of a project, Heroku will use the Node.js buildpack.
  • app.json — a manifest format for describing web apps. It declares environment variables, add-ons, and other information required to run an app on Heroku. It is required to create a “Deploy to Heroku” button.
  • server.js — this file contains all of our server-side code, which implements our REST API. It’s written in Node.js, using the Express framework and the MongoDB Node.js driver.
  • /public directory — this directory contains all of the client-side files which includes the AngularJS code.

See the Sample Application Running

To see a running version of the application this tutorial will create, you can view our running example here: https://sleepy-citadel-45065.herokuapp.com/

Now, let’s follow the tutorial step by step.

Create a New App

Create a new directory for your app and use the cd command to navigate to that directory. From this directory, we’ll create an app on Heroku which prepares Heroku to receive your source code. We’ll use the Heroku CLI to get started.

$ git init
Initialized empty Git repository in /path/.git/
$ heroku create
Creating app... done, stack is cedar-14
https://sleepy-citadel-45065.herokuapp.com/ | https://git.heroku.com/sleepy-citadel-45065.git

When you create an app, a git remote (called heroku) is also created and associated with your local git repository. Heroku also generates a random name (in this case sleepy-citadel-45065) for your app.

Heroku recognizes an app as Node.js by the existence of a package.json file in the root directory. Create a file called package.json and copy the following into it:

{
  "name": "MEAN",
  "version": "1.0.0",
  "description": "A MEAN app that allows users to manage contact lists",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "dependencies": {
    "body-parser": "^1.13.3",
    "express": "^4.13.3",
    "mongodb": "^2.1.6"
  }
}

The package.json file determines the version of Node.js that will be used to run your application on Heroku, as well as the dependencies that should be installed with your application. When an app is deployed, Heroku reads this file and installs the appropriate Node.js version together with the dependencies using the npm install command.

To prepare your system for running the app locally, run this command in your local directory to install the dependencies:

$ npm install

After dependencies are installed, you will be ready to run your app locally.

Provision a MongoDB Database

After you set up your application and file directory, create a MongoDB instance to persist your application’s data. We’ll use the mLab hosted database, a fully managed MongoDB service, to easily provision a new MongoDB database:

When you create a mLab database, you will be given a MongoDB connection string. This string contains the credentials to access your database, so it’s best practice to store the value in a config variable. Let’s go ahead and store the connection string in a config var called MONGODB_URI:

heroku config:set MONGODB_URI=mongodb://your-user:your-pass@host:port/db-name

You can access this variable in Node.js as process.env.MONGODB_URI, which we will do later.

Now that our database is ready, we can start coding.

Connect MongoDB and the App Server Using the Node.js Driver

There are two popular MongoDB drivers that Node.js developers use: the official Node.js driver and an object document mapper called Mongoose that wraps the Node.js driver (similar to a SQL ORM). Both have their advantages, but for this example we will use the official Node.js driver.

Create a file called server.js. In this file we’ll create a new Express application and connect to our mLab database.

var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;

var CONTACTS_COLLECTION = "contacts";

var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());

// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;

// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save database object from the callback for reuse.
  db = database;
  console.log("Database connection ready");

  // Initialize the app.
  var server = app.listen(process.env.PORT || 8080, function () {
    var port = server.address().port;
    console.log("App now running on port", port);
  });
});

// CONTACTS API ROUTES BELOW

There are a few things to note regarding connecting to the database:

  • We want to use our database connection pool as often as possible to best manage our available resources. We initialize the db variable in the global scope so that the connection can be used by all the route handlers.
  • We initialize the app only after the database connection is ready. This ensures that the application won’t crash or error out by trying database operations before the connection is established.

Now our app and database are connected. Next we will implement the RESTful API server by first defining all the endpoints.

Create a RESTful API Server with Node.js and Express

As our first step in creating the API, we define the endpoints (or data) we want to expose. Our contact list app will allow users to perform CRUD operations on their contacts.

The endpoints we’ll need are:

/contacts

Method Description
GET Find all contacts
POST Create a new contact

/contacts/:id

Method Description
GET Find a single contact by ID
PUT Update entire contact document
DELETE Delete a contact by ID

Now we’ll add the routes to our server.js file:

// CONTACTS API ROUTES BELOW

// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
  console.log("ERROR: " + reason);
  res.status(code || 500).json({"error": message});
}

/*  "/contacts"
 *    GET: finds all contacts
 *    POST: creates a new contact
 */

app.get("/contacts", function(req, res) {
});

app.post("/contacts", function(req, res) {
});

/*  "/contacts/:id"
 *    GET: find contact by id
 *    PUT: update contact by id
 *    DELETE: deletes contact by id
 */

app.get("/contacts/:id", function(req, res) {
});

app.put("/contacts/:id", function(req, res) {
});

app.delete("/contacts/:id", function(req, res) {
});

The code creates a skeleton for all of the API endpoints defined above.

Implement the API Endpoints

Next, we’ll add in database logic to properly implement these endpoints.

We’ll first implement the POST endpoint for /contacts, which will allow us to create and save new contacts to the database. Each contact will have the following schema:

{
  "_id": <ObjectId>
  "firstName": <string>,
  "lastName": <string>,
  "email": <string>,
  "phoneNumbers": {
    "mobile": <string>,
    "work": <string>
  },
  "twitterHandle": <string>,
  "addresses": {
    "home": <string>,
    "work": <string>
  }
}

The following code implements the /contacts POST request:

app.post("/contacts", function(req, res) {
  var newContact = req.body;
  newContact.createDate = new Date();

  if (!(req.body.firstName || req.body.lastName)) {
    handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
  }

  db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to create new contact.");
    } else {
      res.status(201).json(doc.ops[0]);
    }
  });
});

To test the POST implementation, deploy the code:

$ git add package.json
$ git add server.js
$ git commit -m 'first commit'
$ git push heroku master

The application is now deployed. Ensure that at least one instance of the app is running:

$ heroku ps:scale web=1

Then, use cURL to issue a POST request:

curl -H "Content-Type: application/json" -d '{"firstName":"Chris", "lastName": "Chang", "email": "support@mlab.com"}' http://your-app-name.herokuapp.com/contacts

We haven’t created our web app yet, but you can confirm that the data was successfully saved to the database by visiting the mLab management portal. Your new contact should be displayed in the “contacts” collection.

Alternatively, you can visit https://mlab.com/databases/your-db-name/collections/contacts and observe your new contact there.

Here is the final version of the server.js file, which implements all of the endpoints:

var express = require("express");
var path = require("path");
var bodyParser = require("body-parser");
var mongodb = require("mongodb");
var ObjectID = mongodb.ObjectID;

var CONTACTS_COLLECTION = "contacts";

var app = express();
app.use(express.static(__dirname + "/public"));
app.use(bodyParser.json());

// Create a database variable outside of the database connection callback to reuse the connection pool in your app.
var db;

// Connect to the database before starting the application server.
mongodb.MongoClient.connect(process.env.MONGODB_URI, function (err, database) {
  if (err) {
    console.log(err);
    process.exit(1);
  }

  // Save database object from the callback for reuse.
  db = database;
  console.log("Database connection ready");

  // Initialize the app.
  var server = app.listen(process.env.PORT || 8080, function () {
    var port = server.address().port;
    console.log("App now running on port", port);
  });
});

// CONTACTS API ROUTES BELOW

// Generic error handler used by all endpoints.
function handleError(res, reason, message, code) {
  console.log("ERROR: " + reason);
  res.status(code || 500).json({"error": message});
}

/*  "/contacts"
 *    GET: finds all contacts
 *    POST: creates a new contact
 */

app.get("/contacts", function(req, res) {
  db.collection(CONTACTS_COLLECTION).find({}).toArray(function(err, docs) {
    if (err) {
      handleError(res, err.message, "Failed to get contacts.");
    } else {
      res.status(200).json(docs);
    }
  });
});

app.post("/contacts", function(req, res) {
  var newContact = req.body;
  newContact.createDate = new Date();

  if (!(req.body.firstName || req.body.lastName)) {
    handleError(res, "Invalid user input", "Must provide a first or last name.", 400);
  }

  db.collection(CONTACTS_COLLECTION).insertOne(newContact, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to create new contact.");
    } else {
      res.status(201).json(doc.ops[0]);
    }
  });
});

/*  "/contacts/:id"
 *    GET: find contact by id
 *    PUT: update contact by id
 *    DELETE: deletes contact by id
 */

app.get("/contacts/:id", function(req, res) {
  db.collection(CONTACTS_COLLECTION).findOne({ _id: new ObjectID(req.params.id) }, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to get contact");
    } else {
      res.status(200).json(doc);
    }
  });
});

app.put("/contacts/:id", function(req, res) {
  var updateDoc = req.body;
  delete updateDoc._id;

  db.collection(CONTACTS_COLLECTION).updateOne({_id: new ObjectID(req.params.id)}, updateDoc, function(err, doc) {
    if (err) {
      handleError(res, err.message, "Failed to update contact");
    } else {
      res.status(204).end();
    }
  });
});

app.delete("/contacts/:id", function(req, res) {
  db.collection(CONTACTS_COLLECTION).deleteOne({_id: new ObjectID(req.params.id)}, function(err, result) {
    if (err) {
      handleError(res, err.message, "Failed to delete contact");
    } else {
      res.status(204).end();
    }
  });
});

Set up Static Files for the Web App

Now that our API is complete we will use it to create our web application. The web app allows users to manage contacts from the browser.

Create a public folder in your project’s root directory and copy over the files from the example app’s public folder. The folder includes HTML templates and our AngularJS code.

As you look through the HTML files, you might notice that there’s some unconventional HTML code, such as “ng-view” in the index.html file:

<div class="container" ng-view>

These extensions are features of AngularJS’s template system. Templates allow us to reuse code and dynamically generate views for the end user.

Build the Web App with AngularJS

We’ll use AngularJS to tie everything together. AngularJS will help us to route user requests, render different views, and send data to and from the database.

Our AngularJS code resides in the /public/js folder in the app.js file. To simplify things, we’ll focus solely on the code that is required to retrieve and display contacts when the default homepage route (/) is requested. Implementing this functionality requires that we:

  • Render the appropriate view and template using the AngularJS routeProvider (index.html and list.html).
  • Fetch the contacts from the database using an AngularJS service (GET /contacts).
  • Pass the data from the service to the view with an AngularJS controller (ListController).

The code looks like the following:

angular.module("contactsApp", ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when("/", {
        templateUrl: "list.html",
        controller: "ListController",
        resolve: {
          contacts: function(Contacts) {
              return Contacts.getContacts();
          }
        }
      })
  })
  .service("Contacts", function($http) {
    this.getContacts = function() {
      return $http.get("/contacts").
        then(function(response) {
            return response;
        }, function(response) {
            alert("Error retrieving contacts.");
        });
    }
  })
  .controller("ListController", function(contacts, $scope) {
    $scope.contacts = contacts.data;
  });

Next, we’ll cover each part of the code and what it does.

Route User Requests with AngularJS routeProvider

The routeProvider module helps us configure routes in AngularJS.

angular.module("contactsApp", ['ngRoute'])
  .config(function($routeProvider) {
    $routeProvider
      .when("/", {
        templateUrl: "list.html",
        controller: "ListController",
        resolve: {
          contacts: function(Contacts) {
              return Contacts.getContacts();
          }
        }
      })
  })

The homepage route consists of a few components:

  • the templateUrl, which specifies which template to display
  • the Contacts service, which requests all of the contacts from the API server
  • the ListController, which allows us to add data to the scope and access it from our views.

Use AngularJS Services to Make Requests to the API Server

An AngularJS service generates an object that can be used by the rest of the application. Our service acts as the client-side wrapper for all of our API endpoints.

The homepage route uses the getContacts function to request the contacts data.

.service("Contacts", function($http) {
  this.getContacts = function() {
    return $http.get("/contacts").
      then(function(response) {
        return response;
      }, function(response) {
        alert("Error retrieving contacts.");
      });
  }

Our service functions leverage the built-in AngularJS $http service to generate HTTP requests. The module also returns a promise, which you can modify to add additional functionality (such as logging).

Note that with the $http service we use relative URL paths (for example, /contacts) as opposed to absolute paths like app-name.herokuapp.com/contacts.

Augment Our Scope Using AngularJS Controllers

So far, we’ve configured our route, defined a template to display, and retrieved our data using our Contacts service. To tie everything together, we’ll create a controller.

.controller("ListController", function(contacts, $scope) {
  $scope.contacts = contacts.data;
})

Our controller adds the contacts data from our service to the homepage scope as a variable named contacts. This allows us to access the data directly from the template (list.html). We can iterate over the contacts data with AngularJS’s built-in ngRepeat directive:

<div class="container">
  <table class="table table-hover">
    <tbody>
      <tr ng-repeat="contact in contacts | orderBy:'lastName'" style="cursor:pointer">
        <td>
          <a ng-href="#/contact/{{contact._id}}">{{ contact.firstName }} {{ contact.lastName }}</a>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Completing the Project

Now that we have an understanding of how we implemented the homepage route in AngularJS, the implementation for the rest of the web app routes can be found in the source project’s /public/js/app.js file. They all require a route definition in the routeProvider, one or more service functions to make the appropriate HTTP requests, and a controller to augment the scope.

Once you have completed the Angular code, deploy the app again:

$ git add server.js
$ git add public
$ git commit -m 'second commit'
$ git push heroku master

Now that the web application component is complete, you can view your app by opening the website from the CLI:

$ heroku open

Summary

In this tutorial, you learned how to:

  • create a RESTful API server in Express and Node.js.
  • connect a MongoDB database to the API server for querying and persisting data.
  • create a rich web app using AngularJS.

We hope that you have seen the power of the MEAN stack to enable the development of common components for today’s web applications.

Notes on Scaling

If you are running a production MEAN application on Heroku, you will need to scale both your application and database as your traffic increases and data size grows. Refer to the Optimizing Node.js Application Concurrency article for best practices on scaling your application. To upgrade your database, see the mLab add-on documentation.

Optional next Steps

As we mentioned previously, this app intentionally omits details you would want to include in a real production application. In particular, we do not implement a user model, user authentication, or robust input validation. Consider adding these features as an additional exercise. If you have any questions about this tutorial, please let us know in the comments below.

  • http://www.anil2u.info/ Anil Kumar Panigrahi

    Thank you for providing good information. I need to try this approach.

  • Zeeshan Dawood

    While trying to go through this tutorial. I was a bit confused on where to put the config variable to set the connection string to database. I was also unclear as to where to set the schema for the the database. Are all those suppose to go in the server.js?

  • Gary Pang

    Hello @zeeshandawood:disqus, Save the connection string on Heroku, not in server.js. This is good practice because it keeps your database password private. Put the config variable (MONGODB_URI) in server.js. Your app can now access the connection string via the config var. Hope this helps!

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in JavaScript, once a week, for free.