Skip to main content

Contact Manager - written in AngularJS, Express and MongoDB - Episode 1

7 min read

Older Article

This article was published 13 years ago. Some information may be outdated or no longer applicable.

I needed a new project to sink my teeth into. The online card game was nearly done, and I wanted something fresh to build and document.

A contact manager felt like the right fit. List contacts, search them, query details. Simple enough to keep focus on the tools: AngularJS, Express, and MongoDB.

This’ll be a two-parter. Part one covers the backend, the bit that pulls JSON data out of MongoDB. Part two tackles the frontend, running Express with AngularJS. Let’s start with the database.

The beauty of pairing MongoDB with JavaScript technologies is that they all speak the same language (JSON, which MongoDB actually calls BSON). Building around these objects becomes second nature. If you haven’t worked with MongoDB, it belongs to the “NoSQL” family. At a high level, it’s a key-value storage system. The example here is for demonstration purposes only, so the database stays deliberately small. I’m not using MongoDB’s native drivers but instead relying on a library called Mongoose. Just by following the quick start guide on that site, you can get a lot done. Here’s my code:

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/contact');
var Schema = mongoose.Schema;

var ContactSchema= new Schema({
    name: { type: String, required: true },
    phone: { type: Number },
});

var ContactModel = mongoose.model('Contact', ContactSchema);

We’re requiring the mongoose package (install it via npm with npm install mongoose). (This tutorial doesn’t cover the installation and setup of MongoDB itself. Refer to this manual for the right setup option.)

Next, we connect to our database. The connect() method automatically creates a document if one doesn’t exist. MongoDB is a document-based database system, so all records live as documents. Documents are the default representation of most user-accessible data structures in the database.

We also define a schema for our Contacts by calling a class that already exists inside mongoose: Schema. In the example above I’m setting up a very simple document with two keys, name (String) and phone (Number). Only a handful of schema types exist. For a full list, refer to the documentation.

Finally, we create a model based on the ContactSchema we’ve just set up. For a full explanation about Models, refer to the Mongoose documentation.

Let me talk about routing at the backend for a moment. I’ll be using Express, so I need to set up the app configuration first.

var express = require('express');
var contacts = require('./contact');
var app = express();

app.listen(1222, '127.0.0.1');

If you run node index.js and navigate to http://host:1222 in your browser, you should see a Cannot GET / message. Don’t worry. That’s normal because we haven’t specified any routes yet. With Express you can define your routes to be ”/” and that would automatically load the index.html file (if it exists) and parse that in the browser. But remember, we’re writing a backend interface that should only produce data, not HTML. Some of you can probably already sense where this is heading. To make it work, I need to go back to my contact.js file and extend it to produce the data I need. Later I’ll add calls to those methods in my index.js file.

That said, add the following to contact.js:

exports.index = function (req, res) {
  return ContactModel.find(function (err, contacts) {
    if (!err) {
      res.jsonp(contacts);
    } else {
      console.log(err);
    }
  });
};

exports.findById = function (req, res) {
  return ContactModel.findById(req.params.id, function (err, contact) {
    if (!err) {
      res.jsonp(contact);
    } else {
      console.log(err);
    }
  });
};

These two methods should be easy to follow. The first returns all entries from our ContactModel (think of it as a SELECT * FROM CONTACTS query if you’re familiar with MySQL). The second returns only one result (the MySQL equivalent being SELECT * FROM CONTACTS WHERE id = int).

We’re ready to call these methods from our index.js. We can do that because all the methods above are defined with the exports tag. Add the following to your code:

app.configure(function () {
  app.use(express.bodyParser());
});
app.get('/', contacts.index);
app.get('/contacts', contacts.index);
app.get('/contacts/:id', contacts.findById);

If you restart your application by stopping the previous instance and re-running node index.js, you should see empty data returned by MongoDB once you navigate to http://host:1222/.

You don’t have any data yet, and the only routing we’ve added to Express uses “get” calls. Let me pause here and explain a few things about RESTful web calls and the GET method. Most of you reading this will already be familiar with GET and POST requests. If you’ve done any sort of web development, you’ve bumped into these methods, especially when dealing with URLs and forms. HTTP as a protocol has a vocabulary of actions referred to as operation methods. The most well-known ones are:

  • GET - request information
  • POST - add information
  • PUT - update existing information
  • DELETE - remove information

(Wondering about the difference between GET and POST? In a GET request, key-value pairs travel via the URL. With a POST request, they’re sent as part of the HTTP request body after the headers.)

Our code above is missing the PUT method, so let’s add that and configure our code to add a contact to the database:

exports.addContact = function (req, res) {
  var contact;
  contact = new ContactModel({
    name: req.body.name,
    phone: req.body.phone,
  });
  contact.save(function (err) {
    if (!err) {
      console.log('created');
    } else {
      console.log(err);
    }
  });

  return res.send(contact);
};

… followed by the routing for our POST request:

app.post('/contacts', contacts.addContact);

Want to test adding a contact? You can do this via the command line for now (since we don’t have a web UI yet). Fire up your terminal and run the following (make sure you’ve restarted the application): curl -i -X POST -H 'Content-Type: application/json' -d '{"name": "Uncle Joe"}' [http://host:1222/contacts](http://host:1222/contacts)

This should produce something like:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 62
Date: Wed, 26 Jun 2013 09:24:05 GMT
Connection: keep-alive

{
  "name": "Uncle Joe",
  "_id": "51cab335b7e1587508000001"
}

If you now navigate to [http://host:1222](http://host:1222) with your web browser, you should see this result returned in JSON format:

[
  {
    "name": "Uncle Joe",
    "_id": "51cab335b7e1587508000001",
    "__v": 0
  }
]

The last two things to wire up are the update and delete methods:

exports.updateContact = function (req, res) {
  return ContactModel.findById(req.params.id, function (err, contact) {
    contact.name = req.body.name;
    contact.phone = req.body.phone;
    contact.save(function (err) {
      if (!err) {
        console.log('updated');
      } else {
        console.log(err);
      }
      res.send(contact);
    });
  });
};

exports.deleteContact = function (req, res) {
  return ContactModel.findById(req.params.id, function (err, contact) {
    return contact.remove(function (err) {
      if (!err) {
        console.log('removed');
        return res.send('');
      } else {
        console.log(err);
      }
    });
  });
};

Don’t forget to update index.js as well:

app.post('/contacts', contacts.addContact);
app.put('/contacts/:id', contacts.updateContact);
app.delete('/contacts/:id', contacts.deleteContact);

Time to test again. Run the following in your terminal: curl -i -X PUT -H 'Content-Type: application/json' -d '{"name":"Uncle Jack"}' [http://host:1222/contacts/put-the-id-here](http://host:1222/contacts/put-the-id-here). This should give you:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 75
Date: Thu, 27 Jun 2013 10:01:53 GMT
Connection: keep-alive
{
  "name": "Uncle Jack",
  "_id": "51caaff064024e5608000001",
  "__v": 0
}

To verify the update worked, query for all contacts by running the curl statement from before.

Now let’s remove Uncle Jack: curl -i -X DELETE [http://host:1222/contacts/put-the-id-here](http://host:1222/contacts/put-the-id-here).

This produces only a ‘200 OK’ HTTP message:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 0
Date: Thu, 27 Jun 2013 10:02:21 GMT
Connection: keep-alive

To verify the deletion worked, query for all contacts again (either via the terminal or your browser).

And that’s it. A fully functional RESTful API capable of handling CRUD operations. In the next article we’ll bolt on the frontend and start displaying and manipulating this data from AngularJS.