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

Share this article

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.

Frequently Asked Questions (FAQs) about Deploying REST API in 30 Minutes with mLab and Heroku

What is the role of mLab in deploying a REST API?

mLab is a fully managed cloud database service that hosts MongoDB databases. In the context of deploying a REST API, mLab provides the database infrastructure where your API data will be stored. It offers features like automated backups, monitoring, web-based management tools, and data redundancy, making it a reliable choice for developers.

How does Heroku facilitate the deployment of a REST API?

Heroku is a cloud platform that lets companies build, deliver, monitor, and scale apps. When deploying a REST API, Heroku acts as the server environment where your API will be hosted. It supports several programming languages and offers a range of services and tools that make the deployment process easier, such as Git integration for version control, add-ons for extending your app’s functionality, and a user-friendly dashboard for managing your apps.

Can I use other database services instead of mLab for deploying my REST API?

Yes, you can use other database services instead of mLab for deploying your REST API. The choice of database service depends on your specific requirements, such as the type of data you’re working with, the scale of your application, and your budget. Other popular cloud database services include Amazon DynamoDB, Google Cloud Firestore, and Microsoft Azure Cosmos DB.

What are the prerequisites for deploying a REST API with mLab and Heroku?

Before you can deploy a REST API with mLab and Heroku, you need to have a basic understanding of how REST APIs work and be familiar with the programming language you’ll be using to build your API. You also need to have accounts on mLab and Heroku, and have the necessary software installed on your computer, such as a code editor and a Git client.

How can I secure my REST API deployed on Heroku?

Securing your REST API is crucial to protect your data and prevent unauthorized access. Heroku provides several security features, such as automated patching, compliance standards, and network isolation. Additionally, you can implement security measures at the application level, such as using HTTPS for all API requests, validating and sanitizing input data, and using authentication and authorization mechanisms.

How can I monitor the performance of my REST API deployed on Heroku?

Heroku offers a range of monitoring tools that can help you track the performance of your REST API. These include Heroku Metrics, which provides information about your app’s usage and performance, and Heroku Alerts, which sends notifications when your app’s performance deviates from its normal behavior. You can also use third-party monitoring tools, such as New Relic or Datadog, which can be integrated with Heroku.

What should I do if my REST API deployed on Heroku is not working as expected?

If your REST API is not working as expected, you can use Heroku’s logging and debugging tools to identify the issue. Heroku captures and aggregates logs from all the components of your app, which you can view and search through the Heroku Dashboard or the Heroku CLI. You can also set up error tracking with services like Sentry or Rollbar, which can provide more detailed information about errors and exceptions.

Can I scale my REST API deployed on Heroku as my application grows?

Yes, Heroku is designed to scale with your application as it grows. You can easily adjust the number of dynos (containers that run your app) to handle more traffic, and use Heroku’s autoscaling feature to automatically add or remove dynos based on your app’s performance. Additionally, mLab offers several plans with different storage and performance capacities, so you can upgrade your database as needed.

How much does it cost to deploy a REST API with mLab and Heroku?

The cost of deploying a REST API with mLab and Heroku depends on the resources you use. Both mLab and Heroku offer free tiers with limited resources, which can be sufficient for small applications or for testing and development purposes. For larger applications or production environments, you may need to choose a paid plan, which varies in price based on the resources and features included.

Can I migrate my REST API to another platform after deploying it on Heroku?

Yes, you can migrate your REST API to another platform after deploying it on Heroku. The process involves exporting your data from mLab, setting up your API on the new platform, and updating your API endpoints to point to the new server. Keep in mind that this can be a complex process and may require downtime, so it’s important to plan carefully and test thoroughly before making the switch.

Chris ChangChris Chang
View Author

Chris is a Developer Advocate at MongoLab, the MongoDB-as-a-Service platform that runs on AWS, Azure, and Google. Learn more about MongoLab: https://mongolab.com/.

Angular TutorialsExpressHerokujameshmean stackmLabmongomongodbnodeNode-JS-Tutorialsrestful apirestful api server
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week