Home Web Development MEAN Blueprints

MEAN Blueprints

By Robert Onodi
books-svg-icon Book
eBook $39.99 $27.98
Print $48.99 $28.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $39.99 $27.98
Print $48.99 $28.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
About this book
The MEAN stack is a combination of the most popular web development frameworks available—MongoDB, Angular, Express, and Node.js used together to offer a powerful and comprehensive full stack web development solution. It is the modern day web dev alternative to the old LAMP stack. It works by allowing AngularJS to handle the front end, and selecting Mongo, Express, and Node to handle the back-end development, which makes increasing sense to forward-thinking web developers. The MEAN stack is great if you want to prototype complex web applications. This book will enable you to build a better foundation for your AngularJS apps. Each chapter covers a complete, single, advanced end-to-end project. You’ll learn how to build complex real-life applications with the MEAN stack and few more advanced projects. You will become familiar with WebSockets and build real-time web applications, as well as create auto-destructing entities. Later, we will combine server-side rendering techniques with a single page application approach. You’ll build a fun project and see how to work with monetary data in Mongo. You will also find out how to a build real-time e-commerce application. By the end of this book, you will be a lot more confident in developing real-time, complex web applications using the MEAN stack.
Publication date:
May 2016
Publisher
Packt
Pages
336
ISBN
9781783553945

 

Chapter 1. Contact Manager

In this chapter, you'll learn how to build a Contact manager application. The application will be divided into two separate parts: one part consisting of the backend, our Node.js API written using Express, and the client application crafted using Angular 2.

Don't worry! This chapter will be more of a guideline, setting up a base project and wrapping your head around TDD (short for Test-driven development) in Node.js. We'll also get to see Angular 2 in action. We are not going to write tests on the client side as there are enough things to accumulate in one chapter.

 

Setting up the base application


The best way to start is with a solid base. That's why we are going to focus on building the base structure of our application. A good base gives you modularity and flexibility and also files should be easily located by you and even your team members.

Always start with something simple and start building around it. As your application grows, you'll probably outgrow your initial application structure, so thinking ahead will bring you big benefits in the long run.

Folder structure

Before jumping in and building your features right away, you should take a moment and sketch out your initial application's structure. In the planning process, a pen and paper should always do it, but I've already saved some time and come up with an initial version:

app/
--controllers/
--middlewares/
--models/
--routes/
config/
--environments/
--strategies/
tests/
--integration/
--unit/
public/
--app/
--src/
--assets/
--typings/
--package.json
--tsconfig.json
--typings.json
package.json
server.js

Let's take a look at a more detailed explanation of our folder structure:

  • app: This folder contains all the server files used in the application:

    • controllers: This folder is going to store the application controllers, mainly the backend business logic.

    • middlewares: In this folder, we'll store all our pieces of functions that will manipulate the request and response object. A good example would be an authentication middleware.

    • models: This folder will store all the backend models.

    • routes: This folder will contain all the routing files, which is where we are going to define all Express routes.

  • config: All application configuration files go here:

    • environments: This folder contains files loaded according to the current environment

    • strategies: All your authentication strategies should go here

  • tests: This folder contains all the tests necessary to test the application backend logic:

    • integration: If something uses external modules, it's good practice to create an integration test

    • unit: This should contain tests for small units of code, such as password hashing

  • public: This should contain all the static files served by our application. I like this separation because it's easy to just tell another web server to handle our static files. Let's say you want nginx to handle static file serving:

    • app: This is our client-side application's folder. All compiled TypeScript files will go here. This folder should be automatically populated.

    • src: This folder contains all the client-side files used to build our application. We are going to use TypeScript to build our Angular application.

    • typings: This contains TypeScript definitions.

Server-side package.json

After setting up the initial folder structure, the next thing to do is to create the package.json file. This file will hold all the application's metadata and dependencies. The package.json file will be placed at the root of our project folder. The path should be contact-manager/package.json:

{
  "name": "mean-blueprints-contact-manager",
  "version": "0.0.9",
  "repository": {
    "type": "git",
    "url": "https://github.com/robert52/mean-blueprints-cm.git"
  },
  "engines": {
    "node": ">=4.4.3"
  },
  "scripts": {
    "start": "node app.js",
    "unit": "node_modules/.bin/mocha tests/unit/ --ui bdd --recursive --reporter spec --timeout 10000 --slow 900",
    "integration": "node_modules/.bin/mocha tests/integration/ --ui bdd --recursive --reporter spec --timeout 10000 --slow 900",
    "less": "node_modules/.bin/autoless public/assets/less public/assets/css --no-watch",
    "less-watch": "node_modules/.bin/autoless public/assets/less public/assets/css"
  },
  "dependencies": {
    "async": "^0.9.2",
    "body-parser": "^1.15.0",
    "connect-mongo": "^1.1.0",
    "express": "^4.13.4",
    "express-session": "^1.13.0",
    "lodash": "^3.10.1",
    "method-override": "^2.3.5",
    "mongoose": "^4.4.12",
    "passport": "^0.2.2",
    "passport-local": "^1.0.0",
    "serve-static": "^1.10.2"
  },
  "devDependencies": {
    "autoless": "^0.1.7",
    "chai": "^2.3.0",
    "chai-things": "^0.2.0",
    "mocha": "^2.4.5",
    "request": "^2.71.0"
  }
}

We added a few scripts to our package.json file to run our unit and integration tests and compile the Less files. You can always use npm to directly run different scripts instead of using build tools such as Grunt or Gulp.

At the time of writing this book, we are using the defined dependencies and their versions. This should do it for now. Let's install them using the following command:

$ npm install

You should see npm pulling a bunch of files and adding the necessary dependencies to the node_modules folder. Wait patiently until everything is installed and done. You will be returned to Command Prompt. Now you should see the node_modules folder created and with all the dependencies in place.

The first application file

Before everything, we need to create a simple configuration file for our environment. Let's create the file in the config folder at contact-manager/config/environments/development.js and add the following content:

'use strict';

module.exports = {
  port: 3000,
  hostname: '127.0.0.1',
  baseUrl: 'http://localhost:3000',
  mongodb: {
    uri: 'mongodb://localhost/cm_dev_db'
  },
  app: {
    name: 'Contact manager'
  },
  serveStatic: true,
  session: {
    type: 'mongo',
    secret: 'u+J%E^9!hx?piXLCfiMY.EDc',
    resave: false,
    saveUninitialized: true
  }
};

Now let's create the main server.js file for our application. This file will be the heart of our application. The file should be in the root of our folder, contact-manager/server.js. Start with the following lines of code:

'use strict';

// Get environment or set default environment to development
const ENV = process.env.NODE_ENV || 'development';
const DEFAULT_PORT = 3000;
const DEFAULT_HOSTNAME = '127.0.0.1';

const http = require('http');
const express = require('express');
const config = require('./config');
const app = express();

var server;

// Set express variables
app.set('config', config);
app.set('root', __dirname);
app.set('env', ENV);

require('./config/mongoose').init(app);
require('./config/models').init(app);
require('./config/passport').init(app);
require('./config/express').init(app);
require('./config/routes').init(app);

// Start the app if not loaded by another module
if (!module.parent) {
  server = http.createServer(app);
  server.listen(
    config.port || DEFAULT_PORT,
    config.hostname || DEFAULT_HOSTNAME,
    () => {
      console.log(`${config.app.name} is running`);
      console.log(`   listening on port: ${config.port}`);
      console.log(`   environment: ${ENV.toLowerCase()}`);
    }
  );
}

module.exports = app;

We define some of our main dependencies and initialize the necessary modules of our application. To modularize things, we are going to put each package of our stack into a separate configuration file. These configuration files will have some logic in them. I like to call them smart configuration files.

Don't worry! We are going to go through each config file one by one. Finally, we will export our Express app instance. If our module is not loaded by another module, for example, a test case, then we can safely start listening to incoming requests.

Creating the Express configuration file

We need to create a configuration file for Express. The file should be created in the config folder at contact-manager/config/express.js and we have to add the following lines of code:

'use strict';

const path = require('path');
const bodyParser = require('body-parser');
const methodOverride = require('method-override');
const serveStatic = require('serve-static');
const session = require('express-session');
const passport = require('passport');
const MongoStore = require('connect-mongo')(session);
const config = require('./index');

module.exports.init = initExpress;

function initExpress(app) {
  const root = app.get('root');
  const sessionOpts = {
    secret: config.session.secret,
    key: 'skey.sid',
    resave: config.session.resave,
    saveUninitialized: config.session.saveUninitialized
  };

  //common express configs
  app.use(bodyParser.urlencoded({ extended: true }));
  app.use(bodyParser.json());
  app.use(methodOverride());
  app.disable('x-powered-by');

  if (config.session.type === 'mongo') {
    sessionOpts.store = new MongoStore({
      url: config.mongodb.uri
    });
  }

  app.use(session(sessionOpts));
  app.use(passport.initialize());
  app.use(passport.session());

  app.use(function(req, res, next) {
    res.locals.app = config.app;

    next();
  });

  // always load static files if dev env
  if (config.serveStatic) {
    app.use(serveStatic(path.join(root, 'public')));
  }
};

You should be familiar with many lines from the preceding code by now, for example, setting the desired body parser of our Express application. Also, we set up the session management, and just in case we set to the server static files, we define the path to the server files.

In a production environment, you should use something different from the default in-memory storage for sessions. That's why we added a special session store, which will store data in MongoDB.

A good practice to get the global environment configuration file is to set a root config file that all application files will load, create a new file called contact-manager/config/index.js, and add this code to it:

'use strict';

var ENV = process.env.NODE_ENV || 'development';
var config = require('./environments/'+ENV.toLowerCase());

module.exports = config;

The preceding code will just load the necessary environment configuration file based on the NODE_ENV process environment variable. If the environment variable is not present, a default development state will be considered for the application. This is a good practice so that we don't make mistakes and connect to the wrong database.

Usually, the NODE_ENV variable can be set when you start your node server; for example, under Unix systems, you can run the following command:

$ NODE_ENV=production node server.js

Setting up mocha for testing

Before we implement any functionality, we are going to write tests for it. Mocha is a testing framework built on Node.js. This approach will give us the advantage of knowing what code we are going to write and testing our Node.js API before even writing a single line of the client application.

If you don't have Mocha, you can install it globally. If you want Mocha to be globally available in your command line, run the following command:

$ npm install -g mocha

Setting up Mongoose

In order to store data in MongoDB, we are going to use Mongoose. Mongoose provides an easy way to define schemas to model application data. We have already included mongoose in the package.json file, so it should be installed.

We need to create a config file for our mongoose library. Let's create our config file contact-manager/config/mongoose.js. First, we start by loading the Mongoose library, getting the appropriate environment config, and establishing a connection with the database. Add the following code to the mongoose.js file:

'use strict';

const mongoose = require('mongoose');
const config = require('./index');

module.exports.init = initMongoose;

function initMongoose(app) {
  mongoose.connect(config.mongodb.uri);

  // If the Node process ends, cleanup existing connections
  process.on('SIGINT', cleanup);
  process.on('SIGTERM', cleanup);
  process.on('SIGHUP', cleanup);

  if (app) {
    app.set('mongoose', mongoose);
  }

  return mongoose;
};

function cleanup() {
  mongoose.connection.close(function () {
    console.log('Closing DB connections and stopping the app. Bye bye.');
    process.exit(0);
  });
}

Also, we are using a cleanup() function to close all connections to the MongoDB database. The preceding code will export the necessary init() function used in the main server.js file.

 

Managing contacts


Now that we have the files necessary to start development and add features, we can start implementing all of the business logic related to managing contacts. To do this, we first need to define the data model of a contact.

Creating the contact mongoose schema

Our system needs some sort of functionality to store the possible clients or just contact persons of other companies. For this, we are going to create a contact schema that will represent the same collection storing all the contacts in MongoDB. We are going to keep our contact schema simple. Let's create a model file in contact-manager/app/models/contact.js, which will hold the schema, and add the following code to it:

'use strict';

const mongoose = require('mongoose');
const Schema = mongoose.Schema;

var ContactSchema = new Schema({
  email:  {
    type: String
  },
  name: {
    type: String
  },
  city: {
    type: String
  },
  phoneNumber: {
    type: String
  },
  company: {
    type: String
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

// compile and export the Contact model
module.exports = mongoose.model('Contact', ContactSchema);

The following table gives a description of the fields in the schema:

Field

Description

email

The e-mail of the contact

name

The full name of the contact

company

The name of the company at which the contact person works

phoneNumber

The full phone number of the person or company

city

The location of the contact

createdAt

The date at which the contact object was created

All our model files will be registered in the following configuration file, found under contact-manager/config/models.js. The final version of this file will look something like this:

'use strict';

module.exports.init = initModels;

function initModels(app) {
  let modelsPath = app.get('root') + '/app/models/';

  ['user', 'contact'].forEach(function(model) {
    require(modelsPath + model);
  });
};

Describing the contact route

In order to communicate with the server, we need to expose routes for client applications to consume. These are going to be endpoints (URIs) that respond to client requests. Mainly, our routes will send a JSON response.

We are going to start by describing the CRUD functionality of the contact module. The routes should expose the following functionalities:

  • Create a new contact

  • Get a contact by ID

  • Get all contacts

  • Update a contact

  • Delete a contact by ID

We are not going to cover bulk insert and delete in this application.

The following table shows how these operations can be mapped to HTTP routes and verbs:

Route

Verb

Description

Data

/contacts

POST

Create a new contact

email, name, company, phoneNumber, and city

/contacts

GET

Get all contacts from the system

 

/contacts/<id>

GET

Get a particular contact

 

/contacts/<id>

PUT

Update a particular contact

email, name, company, phoneNumber, and city

/contacts/<id>

DELETE

Delete a particular contact

 

Following the earlier table as a guide, we are going to describe our main functionality and test using Mocha. Mocha allows us to describe the features that we are implementing by giving us the ability to use a describe function that encapsulates our expectations. The first argument of the function is a simple string that describes the feature. The second argument is a function body that represents the description.

You have already created a folder called contact-manger/tests. In your tests folder, create another folder called integration. Create a file called contact-manager/tests/integration/contact_test.js and add the following code:

'use strict';

/**
 * Important! Set the environment to test
 */
process.env.NODE_ENV = 'test';

const http = require('http');
const request = require('request');
const chai = require('chai');
const userFixture = require('../fixtures/user');
const should = chai.should();

let app;
let appServer;
let mongoose;
let User;
let Contact;
let config;
let baseUrl;
let apiUrl;

describe('Contacts endpoints test', function() {

  before((done) => {
    // boot app
    // start listening to requests
  });

  after(function(done) {
    // close app
    // cleanup database
    // close connection to mongo
  });

  afterEach((done) => {
    // remove contacts
  });

  describe('Save contact', () => {});

  describe('Get contacts', () => {});

  describe('Get contact', function() {});

  describe('Update contact', function() {});

  describe('Delete contact', function() {});
});

In our test file, we required our dependencies and used Chai as our assertion library. As you can see, besides the describe() function, mocha gives us additional methods: before(), after(), beforeEach(), and afterEach().

These are hooks and they can be async or sync, but we are going to use the async version of them. Hooks are useful for preparing preconditions before running tests; for example, you can populate your database with mock data or clean it up.

In the main description body, we used three hooks: before(), after(), and afterEach(). In the before() hook, which will run before any of the describe() functions, we set up our server to listen on a given port, and we called the done() function when the server started listening.

The after() function will run after all the describe() functions have finished running and will stop the server from running. Now, the afterEach() hook will run after each describe() function, and it will grant us the ability to remove all the contacts from the database after running each test.

The final version can be found in the code bundle of the application. You can still follow how we add all the necessary descriptions.

Creating a contact

We also added four to five individual descriptions that will define CRUD operations from the earlier table. First, we want to be able to create a new contact. Add the following code to the test case:

  describe('Create contact', () => {
    it('should create a new contact', (done) => {
      request({
        method: 'POST',
        url: `${apiUrl}/contacts`,
        form: {
          'email': 'jane.doe@test.com',
          'name': 'Jane Doe'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(201);
        body.email.should.equal('jane.doe@test.com');
        body.name.should.equal('Jane Doe');
        done();
      });
    });
  });

Getting contacts

Next, we want to get all contacts from the system. The following code should describe this functionality:

  describe('Get contacts', () => {
    before((done) => {
      Contact.collection.insert([
        { email: 'jane.doe@test.com' },
        { email: 'john.doe@test.com' }
      ], (err, contacts) => {
        if (err) throw err;

        done();
      });
    });

    it('should get a list of contacts', (done) => {
      request({
        method: 'GET',
        url: `${apiUrl}/contacts`,
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(200);
        body.should.be.instanceof(Array);
        body.length.should.equal(2);
        body.should.contain.a.thing.with.property('email', 'jane.doe@test.com');
        body.should.contain.a.thing.with.property('email', 'john.doe@test.com');
        done();
      });
    });
  });

As you can see, we've also added a before() hook in the description. This is absolutely normal and can be done. Mocha permits this behavior in order to easily set up preconditions. We used a bulk insert, Contact.collection.insert(), to add data into MongoDB before getting all the contacts.

Getting a contact by ID

When getting a contact by ID, we would also want to check whether the inserted ID meets our ObjectId criteria. If a contact is not found, we will want to return a 404 HTTP status code:

  describe('Get contact', function() {
    let _contact;

    before((done) => {
      Contact.create({
        email: 'john.doe@test.com'
      }, (err, contact) => {
        if (err) throw err;

        _contact = contact;
        done();
      });
    });

    it('should get a single contact by id', (done) => {
      request({
        method: 'GET',
        url: `${apiUrl}/contacts/${_contact.id}`,
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(200);
        body.email.should.equal(_contact.email);
        done();
      });
    });

    it('should not get a contact if the id is not 24 characters', (done) => {
      request({
        method: 'GET',
        url: `${apiUrl}/contacts/U5ZArj3hjzj3zusT8JnZbWFu`,
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(404);
        done();
      });
    });
  });

We used the .create() method. It's more convenient to use it for single inserts, to prepopulate the database with data. When getting a single contact by ID we want to ensure that it's a valid ID, so we added a test which should reflect this and get a 404 Not Found response if it's invalid, or no contact was found.

Updating a contact

We also want to be able to update an existing contact with a given ID. Add the following code to describe this functionality:

  describe('Update contact', () => {
    let _contact;

    before((done) => {
      Contact.create({
        email: 'jane.doe@test.com'
      }, (err, contact) => {
        if (err) throw err;

        _contact = contact;
        done();
      });
    });

    it('should update an existing contact', (done) => {
      request({
        method: 'PUT',
        url: `${apiUrl}/contacts/${_contact.id}`,
        form: {
          'name': 'Jane Doe'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(200);
        body.email.should.equal(_contact.email);
        body.name.should.equal('Jane Doe');
        done();
      });
    });
  });

Removing a contact

Finally, we'll describe the remove contact operation (DELETE from CRUD) by adding the following code:

  describe('Delete contact', () => {
    var _contact;

    before((done) => {
      Contact.create({
        email: 'jane.doe@test.com'
      }, (err, contact) => {
        if (err) throw err;

        _contact = contact;
        done();
      });
    });

    it('should update an existing contact', (done) => {
      request({
        method: 'DELETE',
        url: `${apiUrl}/contacts/${_contact.id}`,
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(204);
        should.not.exist(body);
        done();
      });
    });
  });

After deleting a contact, the server should respond with an HTTP 204 No Content status code, meaning that the server has successfully interpreted the request and processed it, but no content should be returned due to the fact that the contact was deleted successfully.

Running our tests

Suppose we run the following command:

$ mocha test/integration/contact_test.js

At this point, we will get a bunch of HTTP 404 Not Found status codes, because our routes are not implemented yet. The output should be similar to something like this:

  Contact
    Save contact
      1) should save a new contact
    Get contacts
      2) should get a list of contacts
    Get contact
      3) should get a single contact by id
      √ should not get a contact if the id is not 24 characters
    Update contact
      4) should update an existing contact
    Delete contact
      5) should update an existing contact

  1 passing (485ms)
  5 failing
  1) Contact Save contact should save a new contact:

      Uncaught AssertionError: expected 404 to equal 201
      + expected - actual

      +201
      -404

Implementing the contact routes

Now, we'll start implementing the contact CRUD operations. We'll begin by creating our controller. Create a new file, contact-manager/app/controllers/contact.js, and add the following code:

'use strict';

const _ = require('lodash');
const mongoose = require('mongoose');
const Contact = mongoose.model('Contact');
const ObjectId = mongoose.Types.ObjectId;

module.exports.create = createContact;
module.exports.findById = findContactById;
module.exports.getOne = getOneContact;
module.exports.getAll = getAllContacts;
module.exports.update = updateContact;
module.exports.remove = removeContact;

function createContact(req, res, next) {
  Contact.create(req.body, (err, contact) => {
    if (err) {
      return next(err);
    }

    res.status(201).json(contact);
  });
}

What the preceding code does is export all methods of the controller for CRUD operations. To create a new contact, we use the create() method from the Contact schema.

We are returning a JSON response with the newly created contact. In case of an error, we just call the next() function with the error object. We will add a special handler to catch all of our errors later.

Let's create a new file for our routes, contact-manager/app/routes/contacts.js. The following lines of code should be a good start for our router:

'use strict';

const express = require('express');
const router = express.Router();
const contactController = require('../controllers/contact');

router.post('/contacts', auth.ensured, contactController.create);

module.exports = router;

Suppose we run our test now using this, like:

$ mocha tests/integration/contact_test.js

We should get something similar to the following:

Contact
  Create contact
      √ should save a new contact
  Get contacts
      1) should get a list of contacts
  Get contact
      2) should get a single contact by id
      √ should not get a contact if the id is not 24 characters
    Update contact
      3) should update an existing contact
  Delete contact
      4) should update an existing contact


  2 passing (502ms)
  4 failing

Adding all endpoints

Next, we will add the rest of the routes, by adding the following code into the contact-manager/app/routes/contact.js file:

router.param('contactId', contactController.findById);

router.get('/contacts', auth.ensured, contactController.getAll);
router.get('/contacts/:contactId', auth.ensured, contactController.getOne);
router.put('/contacts/:contactId', auth.ensured, contactController.update);
router.delete('/contacts/:contactId', auth.ensured, contactController.remove);

We defined all the routes and also added a callback trigger to the contactId route parameter. In Express, we can add callback triggers on route parameters using the param() method with the name of a parameter and a callback function.

The callback function is similar to any normal route callback, but it gets an extra parameter representing the value of the route parameter. A concrete example would be as follows:

app.param('contactId', function(req, res, next, id) { 
  // do something with the id ...
});

Following the preceding example, when :contactId is present in a route path, we can map a contact loading logic and provide the contact to the next handler.

Finding a contact by ID

We are going to add the rest of the missing functionalities in our controller file, located at contact-manager/app/controllers/contact.js:

function findContactById(req, res, next, id) {
  if (!ObjectId.isValid(id)) {
    res.status(404).send({ message: 'Not found.'});
  }

  Contact.findById(id, (err, contact) => {
    if (err) {
      next(err);
    } else if (contact) {
      req.contact = contact;
      next();
    } else {
      next(new Error('failed to find contact'));
    }
  });
}

The preceding function is a special case. It will get four parameter, and the last one will be the ID matching the triggered parameters value.

Getting contact information

To get all contacts, we are going to query the database. We will sort our results based on the creation date. One good practice is to always limit your returned dataset's size. For that, we use a MAX_LIMIT constant:

function getAllContacts(req, res, next) {
  const limit = +req.query.limit || MAX_LIMIT;
  const skip = +req.query.offset || 0;
  const query = {};

  if (limit > MAX_LIMIT) {
    limit = MAX_LIMIT;
  }

  Contact
  .find(query)
  .skip(skip)
  .limit(limit)
  .sort({createdAt: 'desc'})
  .exec((err, contacts) => {
    if (err) {
      return next(err);
    }

    res.json(contacts);
  });
}

To return a single contact, you can use the following code:

function getOneContact(req, res, next) {
  if (!req.contact) {
    return next(err);
  }

  res.json(req.contact);
}

Theoretically, we'll have the :contactId parameter in a route definition. In that case, the param callback is triggered, populating the req object with the requested contact.

Updating a contact

The same principle is applied when updating a contact; the requested entity should be populated by the param callback. We just need to assign the incoming data to the contact object and save the changes into MongoDB:

function updateContact(req, res, next) {
  let contact = req.contact;
  _.assign(contact, req.body);

  contact.save((err, updatedContact) => {
    if (err) {
      return next(err);
    }

    res.json(updatedContact);
  });
}

Removing a contact

Removing a contact should be fairly simple, as it has no dependent documents. So, we can just remove the document from the database, using the following code:

function removeContact(req, res, next) {
  req.contact.remove((err) => {
    if (err) {
      return next(err);
    }

    res.status(204).json();
  });
}

Running the contact test

At this point, we should have implemented all the requirements for managing contacts on the backend. To test everything, we run the following command:

$ mocha tests/integration/contact.test.js

The output should be similar to this:

  Contact

    Save contact
      √ should save a new contact
    Get contacts
      √ should get a list of contacts
    Get contact
      √ should get a single contact by id
      √ should not get a contact if the id is not 24 characters
    Update contact
      √ should update an existing contact
    Delete contact
      √ should update an existing contact


  6 passing (576ms)

This means that all the tests have passed successfully and we have implemented all the requirements.

 

Securing your application routes


You probably don't want to let anyone see your contacts, so it's time to secure your endpoints. There are many strategies that we can use to authenticate trusted users in an application. We are going to use a classic, state-full e-mail and password based authentication. This means that the session will be stored on the server side.

Remember we discussed at the beginning of the chapter how we are going to store our session on the server side? We choose two integrations, one with default in-memory session management and one that stores sessions in MongoDB. Everything is configurable from the environment configuration file.

When it comes to handling authentication in Node.js, a good go-to module is Passport, which is a piece of authentication middleware. Passport has a comprehensive set of authentication strategies using a simple username-and-password combination for Facebook, Google, Twitter, and many more.

We have already added this dependency to our application and made the necessary initializations in the express configuration file. We still need to add a few things, but before that, we have to create some reusable components in our backend application. We are going to create a helper file that will ease our interactions with passwords.

Describing the password helper

Before we dive deeper into the authentication mechanism, we need to be able to store in MongoDB a password hash instead of the plain password. We want to create a helper for this task that enables us to make operations related to passwords.

Create a new folder in the tests folder, named unit. Add a new file, contact-manager/tests/unit/password.test.js, and then add the following code to it:

'use strict';

const chai = require('chai');
const should = chai.should();
const passwordHelper = require('../../app/helpers/password');

describe('Password Helper', () => {
});

In our main description body, we are going to add segments that represent our features in more detail. Add this code:

describe('#hash() - password hashing', () => {
});
describe('#verify() - compare a password with a hash', () => {
});

Mocha also provides an it() function, which we are going to use to set up a concrete test. The it() function is very similar to describe(), except that we put only what the feature is supposed to do. For assertion, we are going to use the Chai library. Add the following code to the tests/unit/password.test.js file:

  describe('#hash() - password hashing', () => {
    it('should return a hash and a salt from a plain string', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        should.exist(hash);
        should.exist(salt);
        hash.should.be.a('string');
        salt.should.be.a('string');
        hash.should.not.equal('P@ssw0rd!');
        done();
      });
    });

    it('should return only a hash from a plain string if salt is given', (done) => {
      passwordHelper.hash('P@ssw0rd!', 'secret salt', (err, hash, salt) => {
        if (err) throw err;

        should.exist(hash);
        should.not.exist(salt);
        hash.should.be.a('string');
        hash.should.not.equal('P@ssw0rd!');
        done();
      });
    });

    it('should return the same hash if the password and salt ar the same', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.hash('P@ssw0rd!', salt, function(err, hashWithSalt) {
          if (err) throw err;

          should.exist(hash);
          hash.should.be.a('string');
          hash.should.not.equal('P@ssw0rd!');
          hash.should.equal(hashWithSalt);
          done();
        });
      });
    });
  });

The passwordHelper should also test whether a password matches the given hash and salt combo. For this, we are going to add the following describe method:

  describe('#verify() - compare a password with a hash', () => {
    it('should return true if the password matches the hash', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.verify('P@ssw0rd!', hash, salt, (err, result) => {
          if (err) throw err;

          should.exist(result);
          result.should.be.a('boolean');
          result.should.equal(true);
          done();
        });
      });
    });

    it('should return false if the password does not matches the hash', (done) => {
      passwordHelper.hash('P@ssw0rd!', (err, hash, salt) => {
        if (err) throw err;

        passwordHelper.verify('password!', hash, salt, (err, result) => {
          if (err) throw err;

          should.exist(result);
          result.should.be.a('boolean');
          result.should.equal(false);
          done();
        });
      });
    });
  });

Implementing the password helper

We will implement our password helper in the following file: contact-manager/app/helpers/password.js.

The first description of our password helper describes a function that creates a hash from a plain password. In our implementation, we will use a key derivation function that will compute a hash from our password, also known as key stretching.

We are going to use the pbkdf2 function from the built-in Node.js crypto library. The asynchronous version of the function takes a plain password and applies an HMAC digest function. We will use sha256 to get a derived key of a given length, combined with a salt through a number of iterations.

We want to use the same hashing function for both cases: when we already have a password hash and a salt and when we have only a plain password. Let's see the final code for our hashing function. Add the following:

'use strict';

const crypto = require('crypto');
const len = 512;
const iterations = 18000;
const digest = 'sha256';

module.exports.hash = hashPassword;
module.exports.verify = verify;

function hashPassword(password, salt, callback) {
  if (3 === arguments.length) {
    crypto.pbkdf2(password, salt, iterations, len, digest, (err, derivedKey) => {
      if (err) {
        return callback(err);
      }

      return callback(null, derivedKey.toString('base64'));
    });
  } else {
    callback = salt;
    crypto.randomBytes(len, (err, salt) => {
      if (err) {
        return callback(err);
      }

      salt = salt.toString('base64');
      crypto.pbkdf2(password, salt, iterations, len, digest, (err, derivedKey) => {
        if (err) {
          return callback(err);
        }

        callback(null, derivedKey.toString('base64'), salt);
      });
    });
  }
}

Let's see what we get if we run our tests now. Run the following command:

$ mocha tests/unit/password.test.js

The output should be similar to this:

  Password Helper
    #hash() - password hashing
      √ should return a hash and a salt from a plain string (269ms)
      √ should return only a hash from a plain string if salt is given (274ms)
      √ should return the same hash if the password and salt are the same (538ms)

3 passing (2s)

As you can see, we have successfully implemented our hashing function. All the requirements from the test case have passed. Notice that it takes up to 2 seconds to run the tests. Don't worry about this; it's because of the key stretching function taking time to generate the hash from the password.

Next, we are going to implement the verify() function, which checks whether a password matches an existing user's password-hash-and-salt combination. From the description in our tests, this function accepts four parameters: the plain password, a hash that was generated using the third salt parameter, and a callback function.

The callback gets two arguments: err and result. The result can be true or false. This will reflect whether the password matches the existing hash or not. Considering the constraints from the tests and the preceding explanation, we can append the following code to our password.helpr.js file:

function verify(password, hash, salt, callback) {
  hashPassword(password, salt, (err, hashedPassword) => {
    if (err) {
      return callback(err);
    }

    if (hashedPassword === hash) {
      callback(null, true);
    } else {
      callback(null, false);
    }
  });
}

By now, we should have implemented all the specifications from our tests.

Creating the user Mongoose schema

In order to grant access to users in the application, we need to store them in a MongoDB collection. We'll create a new file called contact-manager/app/models/user.model.js and add the following code:

'use strict';

const mongoose = require('mongoose');
const passwordHelper = require('../helpers/password');
const Schema = mongoose.Schema;
const _ = require('lodash');

var UserSchema = new Schema({
  email:  {
    type: String,
    required: true,
    unique: true
  },
  name: {
    type: String
  },
  password: {
    type: String,
    required: true,
    select: false
  },
  passwordSalt: {
    type: String,
    required: true,
    select: false
  },
  active: {
    type: Boolean,
    default: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

The following table gives a description of the fields in the schema:

Field

Description

email

The e-mail of the user. This is used to identify the user. E-mails will be unique in the system.

name

The full name of the user.

password

This is the password provided by the user. It will not be stored in plaintext in the database but in a hashed form instead.

passwordSalt

Every password will be generated using a unique salt for the given user.

active

This specifies the state of the user. It can be active or inactive.

createdAt

The date when the user was created.

Describing the authentication method from the user model

We'll describe a user authentication method. It will check whether a user has valid credentials. The following file, contact-manager/tests/integration/user.model.test.js, should contain all the test cases regarding the User model. These lines of code will test the authenticate() method:

  it('should authenticate a user with valid credentials', done => {
    User.authenticate(newUserData.email, newUserData.password, (err, user) => {
      if (err) throw err;

      should.exist(user);
      should.not.exist(user.password);
      should.not.exist(user.passwordSalt);
      user.email.should.equal(newUserData.email);
      done();
    });
  });

  it('should not authenticate user with invalid credentials', done => {
    User.authenticate(newUserData.email, 'notuserpassowrd', (err, user) => {
      if (err) throw err;

      should.not.exist(user);
      done();
    });
  });

Implementing the authentication method

Mongoose lets us add static methods to compiled models from schemas. The authenticate() method will search for a user in the database by its e-mail and use the password helper's verify() function to check whether the sent password is a match.

Add the following lines of code to the contact-manager/app/models/user.js file:

UserSchema.statics.authenticate = authenticateUser;

function authenticateUser(email, password, callback) {
  this
  .findOne({ email: email })
  .select('+password +passwordSalt')
  .exec((err, user) => {
    if (err) {
      return callback(err, null);
    }

    // no user found just return the empty user
    if (!user) {
      return callback(err, user);
    }

    // verify the password with the existing hash from the user
    passwordHelper.verify(
      password,
      user.password,
      user.passwordSalt,
      (err, result) => {
        if (err) {
          return callback(err, null);
        }

        // if password does not match don't return user
        if (result === false) {
          return callback(err, null);
        }

        // remove password and salt from the result
        user.password = undefined;
        user.passwordSalt = undefined;
        // return user if everything is ok
        callback(err, user);
      }
    );
  });
}

In the preceding code, when selecting the user from MongoDB, we explicitly selected the password and passwordSalt fields. This was necessary because we set the password and passwordSalt fields to not be selected in the query result. Another thing to note is that we want to remove the password and salt from the result when returning the user.

Authentication routes

In order to authenticate in the system we are building, we need to expose some endpoints that will execute the necessary business logic to authenticate a user with valid credentials. Before jumping into any code, we are going to describe the desired behavior.

Describing the authentication routes

We are only going to take a look at a partial code from the integration test of the authentication functionality, found in contact-manager/tests/integration/authentication.test.js. It should look something like this:

  describe('Sign in user', () => {
    it('should sign in a user with valid credentials', (done) => {
      request({
        method: 'POST',
        url: baseUrl + '/auth/signin',
        form: {
          'email': userFixture.email,
          'password': 'P@ssw0rd!'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(200);
        body.email.should.equal(userFixture.email);
        should.not.exist(body.password);
        should.not.exist(body.passwordSalt);
        done();
      });
    });

    it('should not sign in a user with invalid credentials', (done) => {
      request({
        method: 'POST',
        url: baseUrl + '/auth/signin',
        form: {
          'email': userFixture.email,
          'password': 'incorrectpassword'
        },
        json:true
      }, (err, res, body) => {
        if (err) throw err;

        res.statusCode.should.equal(400);
        body.message.should.equal('Invalid email or password.');
        done();
      });
    });
  });

So, we've described an auth/signin endpoint; it will authenticate a user using an e-mail-and-password combination. We are testing two scenarios. The first one is when a user has valid credentials and the second is when an incorrect password is sent.

Integrating Passport

We mentioned Passport earlier in the chapter and added some basic logic for this purpose, but we still need to make a proper integration. The Passport module should already be installed and the session management is already in place. So next, we need to create a proper configuration file, contact-manager/config/passport.js, and add the following:

'use strict';

const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('User');

module.exports.init = initPassport;

function initPassport(app) {
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    User.findById(id, done);
  });

  // load strategies
  require('./strategies/local').init();
}

For each subsequent request, we need to serialize and deserialize the user instance to and from the session. We are only going to serialize the user's ID into the session. When subsequent requests are made, the user's ID is used to find the matching user and restore the data in req.user.

Passport gives us the ability to use different strategies to authenticate our users. We are only going to use e-mail and password to authenticate a user. To keep everything modular, we are going to move the strategies into separate files. The so-called local strategy, which will be used to authenticate users using an e-mail and a password, is going to be in the contact-manager/config/strategies/local.js file:

'use strict';

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('mongoose').model('User');

module.exports.init = initLocalStrategy;

function initLocalStrategy() {
  passport.use('local', new LocalStrategy({
      usernameField: 'email',
      passwordField: 'password'
    },
    (email, password, done) => {
      User.authenticate(email, password, (err, user) => {
        if (err) {
          return done(err);
        }

        if (!user) {
          return done(null, false, { message: 'Invalid email or password.' });
        }

        return done(null, user);
      });
    }
  ));
}

Implementing the authentication routes

Now that we have passport up and running, we can define our authentication controller logic and a proper route to sign in users. Create a new file called contact-manager/app/controllers/authentication.js:

'use strict';

const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('User');

module.exports.signin = signin;

function signin(req, res, next) {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      return next(err);
    }

    if (!user) {
      return res.status(400).send(info);
    }

    req.logIn(user, (err) => {
      if (err) {
        return next(err);
      }

      res.status(200).json(user);
    });
  })(req, res, next);
}

Here, we use the .authenticate() function from Passport to check a user's credentials using the local strategy implemented earlier. Next, we are going to add the authentication route, create a new file called contact-manager/app/routes/auth.js, and add the following lines of code:

'use strict';

var express = require('express');
var router = express.Router();
var authCtrl = require('../controllers/authentication');

router.post('/signin', authCtrl.signin);
router.post('/register', authCtrl.register);

module.exports = router;

Note that we skipped the register user functionality, but don't worry! The final bundled project source code will have all of the necessary logic.

Restricting access to contacts routes

We created all the requirements to authenticate our users. Now it's time to restrict access to some of the routes, so technically we are going to create a simple ACL. To restrict access, we are going to use a piece of middleware that will check whether users are authenticated or not.

Let's create our middleware file, contact-manager/app/middlewares/authentication.js. This should contain these lines of carefully crafted code:

'use strict';

module.exports.ensured = ensureAuthenticated;

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }

  res.status(401).json({
    message: 'Please authenticate.'
  });
}

We have already added the necessary logic to restrict users to the contact routes; that was when we first created them. We succeeded in adding all the necessary pieces of code to manage contacts and restrict access to our endpoints. Now we can continue and start building our Angular 2 application.

 

Integrating Angular 2 into our application


The frontend application is going to be built using Angular 2. At the time of writing this book, the project is still in beta, but it will come in handy to start playing around with Angular and have a good understanding of the environment. Most of the code will follow the official docs view of the tooling and integration methods.

When we first described our folder structure, we saw a package.json file for the client application too. Let's take a look at it, found under the contact-manager/public/package.json path:

{
  "private": true,
  "name": "mean-blueprints-contact-manager-client",
  "dependencies": {
    "systemjs": "^0.19.25",
    "es6-shim": "^0.35.0",
    "es6-promise": "^3.0.2",
    "rxjs": "^5.0.0-beta.2",
    "reflect-metadata": "^0.1.2",
    "zone.js": "^0.6.6",
    "angular2": "^2.0.0-beta.14"
  },
  "devDependencies": {
    "typings": "^0.7.12",
    "typescript": "^1.8.9"
  }
}

To install the necessary dependencies, just use the following command:

$ npm install

You will see npm pulling down different packages, specified in the package.json file.

As you can see, we'll use TypeScript in our client-side application. If you have installed it globally, you can use the following command to compile and watch for changes to your .ts files:

$ tsc -w

Only the most important parts of the application will be discussed. The rest of the necessary files and folders can be found in the final bundled source code.

 

Granting access to our application


We have restricted access to our API's endpoints, so now we have to grant users sign-in functionality from the client application. I like to group the Angular 2 application files based on their domain context. So, for example, all our authentication, registration, and business logic should go into a separate folder; we can call it auth.

If your module directory grows, it's good practice to break it down into separate folders based on their context by type. There is no magic number for the file count. Usually, you will get a good feeling when it's time to move files around. Your files should always be easy to locate and give you enough information from their placement in a certain context.

AuthService

We are going to use AuthService to implement the data access layer and make calls to the backend. This service is going to be the bridge between our API's sign-in and register features. Create a new file called contact-manager/src/auth/auth.service.ts, and add the following TypeScript code into it:

import { Injectable } from 'angular2/core';
import { Http, Response, Headers } from 'angular2/http';
import { contentHeaders } from '../common/headers';

@Injectable()
export class AuthService {
  private _http: Http;

  constructor(http: Http) {
    this._http = http;
  }
}

We import the necessary modules, define the AuthService class, and export it. The Injectable marker metadata will mark our class to be available to be injected. In order to communicate with the backend, we use the HTTP service. Don't forget to add the HTTP_PROVIDERS when bootstrapping the application so that the service is available to be injected in the whole application.

To sign in a user, we are going to add the following method:

  public signin(user: any) {
    let body = this._serialize(user);

    return this._http
    .post('/auth/signin', body, { headers: contentHeaders })
    .map((res: Response) => res.json());
  }

We can use the .map() operator to transform the response into a JSON file. When performing HTTP requests, this will return an Observable. You have probably already figured it out—we are going to use RxJs (Reactive Extensions) heavily, which is a third-party library favored by Angular.

RxJs implements asynchronous observable pattern. In other words, it enables you to work with asynchronous data streams and apply different operators. Observables are used widely in Angular applications. At the time of writing this book, Angular 2 exposes a stripped-down version of the Observable module from RxJs.

Don't worry; we'll get familiar with this technique and the benefits of it as we dive further into the book. Now let's continue with the rest of the missing methods we want to expose:

  public register(user: any) {
    let body = this._serialize(user);

    return this._http
    .post('/auth/register', body, { headers: contentHeaders })
    .map((res: Response) => res.json());
  }

  private _serialize(data) {
    return JSON.stringify(data);
  }

We added the register() method to our service, which will handle user registration. Also note that we moved our serialization into a separate private method. I've left this method in the same class so that it's easier to follow, but you can move it into a helper class.

User sign-in component

For a start, we are going to implement the sign-in component. Let's create a new file called contact-manager/public/src/auth/sigin.ts and add the following lines of TypeScript code:

import { Component } from 'angular2/core';
import { Router, RouterLink } from 'angular2/router';
import { AuthService } from './auth.service';

export class Signin {
  private _authService: AuthService;
  private _router: Router;

  constructor(
    authService: AuthService,
    router: Router
  ) {
    this._authService = authService;
    this._router = router;
  }

  signin(event, email, password) {
    event.preventDefault();

    let data = { email, password };

    this._authService
    .signin(data)
    .subscribe((user) => {
      this._router.navigateByUrl('/');
    }, err => console.error(err));
  }
}

We still need to add the Component annotation before our Signin class:

@Component({
    selector: 'signin',
    directives: [
      RouterLink
    ],
    template: `
      <div class="login jumbotron center-block">
        <h1>Login</h1>
        <form role="form" (submit)="signin($event, email.value, password.value)">
          <div class="form-group">
            <label for="email">E-mail</label>
            <input type="text" #email class="form-control" id="email" placeholder="enter your e-mail">
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input type="password" #password class="form-control" id="password" placeholder="now your password">
          </div>
          <button type="submit" class="button">Submit</button>
          <a href="#" [routerLink]="['Register']">Click here to register</a>
        </form>
      </div>
    `
})

The Signin component is going to be our sign-in form and it uses the AuthService to communicate with the backend. In the component's template, we are using local variables marked with a # sign for the email and password fields.

As we said earlier, the HTTP service returns an Observable when making a request. This is the reason we can subscribe to the response generated by the requests made from our AuthService. On successful authentication, the user is redirected to the default home path.

The Register component will look similar to the Signin component, so there is no need to detail this scenario. The final version of the auth module will be available in the source code.

Custom HTTP service

In order to restrict access to our API endpoints, we have to make sure that, if a request is unauthorized, we redirect the user to the sign-in page. Angular 2 has no support for Interceptors and we don't want to add a handler for each request we integrate into our services.

A more convenient solution would be to build our own custom service on top of the built-in HTTP service. We could call it AuthHttp, from authorized HTTP requests. Its purpose would be to check whether a request returned a 401 Unauthorized HTTP status code.

I would like to take this thought even further and bring a hint of reactive programming, because we are already using RxJS. So, we can benefit from the full set of functionalities it provides. Reactive programming is oriented around data. Streams of data propagate in your application and it reacts to those changes.

Let's get to business and start building our custom service. Create a file called contact-manager/public/src/auth/auth-http.ts. We are going to add a few lines of code:

import { Injectable } from 'angular2/core';
import { Http, Response, Headers, BaseRequestOptions, Request, RequestOptions, RequestOptionsArgs, RequestMethod } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/Subject/BehaviorSubject';

@Injectable()
export class AuthHttp {
  public unauthorized: Subject<Response>;
  private _http: Http;

  constructor(http: Http) {
    this._http = http;
    this.unauthorized = new BehaviorSubject<Response>(null);
  }
}

There are a few things we imported at the top of the file. We'll need all of them in this module. We defined a public property named unauthorized, which is a Subject. A Subject is both an Observable and Observer. This means that we can subscribe our subject to a backend data source and also all observers can subscribe to the subject.

In our case, the subject will be a proxy between our data source and all the subscribed observers. If a request is unauthorized, all subscribers get notified with the change. This enables us to just subscribe to the subject and redirect the user to the sign-in page when we detect an unauthorized request.

To succeed in doing this, we have to add a few more methods to our AuthHttp service:

  private request(requestArgs: RequestOptionsArgs, additionalArgs?: RequestOptionsArgs) {
    let opts = new RequestOptions(requestArgs);

    if (additionalArgs) {
      opts = opts.merge(additionalArgs);
    }

    let req:Request = new Request(opts);

    return this._http.request(req).catch((err: any) => {
      if (err.status === 401) {
        this.unauthorized.next(err);
      }

      return Observable.throw(err);
    });
  }

The preceding method creates a new request with the desired RequestOptions and invokes the request method from the base HTTP service. Additionally, the catch method captures all requests with status code not 200-level.

Using this technique, we can send the unauthorized request to all subscribers by using our unauthorized subject. Now that we have our private request method, we just need to add the rest of the public HTTP methods:

  public get(url: string, opts?: RequestOptionsArgs) {
    return this.request({ url: url, method: RequestMethod.Get}, opts);
  }

  public post(url: string, body?: string, opts?: RequestOptionsArgs) {
    return this.request({ url: url, method: RequestMethod.Post, body: body}, opts);
  }

  public put(url: string, body?: string, opts?: RequestOptionsArgs) {
    return this.request({ url: url, method: RequestMethod.Put, body: body}, opts);
  }

  // rest of the HTTP methods ...

I've added only the most commonly used methods; the rest is available in the full version. The preceding code calls our request method and sets the necessary options for each request type. Theoretically, we have created a façade to handle unauthorized requests.

I think we've made good progress and it's time to move on to the rest of the modules of our contact manager application.

 

The Contact module


This module will hold all the necessary files to manage contacts. As we discussed earlier, we are grouping our files by context, related to their domain. The starting point of our module will be the data layer, which means we'll start implementing the necessary service.

Contact service

Our contact service will have basic CRUD operations and Observable streams to subscribe to. This implementation will use the backend API built using Node.js and Express, but it can be converted anytime to a WebSocket-based API with little effort.

Create a new service file called contact-manager/src/contact/contact.service.ts and add the following code:

import { Injectable } from 'angular2/core';
import { Response, Headers } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
import { contentHeaders } from '../common/headers';
import { AuthHttp } from '../auth/auth-http';
import { Contact } from '../contact';

type ObservableContacts = Observable<Array<Contact>>;
type ObservableContact = Observable<Contact>;

const DEFAULT_URL = '/api/contacts';

@Injectable()
export class ContactService {
  public contact: ObservableContact;
  public contacts: ObservableContacts;

  private _authHttp: AuthHttp;
  private _dataStore: { contacts: Array<Contact>, contact: Contact };
  private _contactsObserver: any;
  private _contactObserver: any;
  private _url: string;

  constructor(authHttp: AuthHttp) {
    this._authHttp = authHttp;
    this._url = DEFAULT_URL;
    this._dataStore = { contacts: [], contact: new Contact() };
    this.contacts = new Observable(
      observer => this._contactsObserver = observer
    ).share();
    this.contact = new Observable(
      observer => this._contactObserver = observer
    ).share();
  }
}

In the contact service, we have a few moving parts. First we defined our Observables so that any other component or module can subscribe and start getting the streams of data.

Second, we declared a private data store. This is where we are going to store our contacts. This is good practice as you can easily return all resources from memory.

Also, in our service, we are going to keep private the returned Observers when new instances of Observables are generated. Using the Observers, we can push new data streams to our Observables.

In our public methods, we are going to expose the get all contacts, get one, update, and delete functionalities. To get all contacts, we are going to add the following method to our ContactService:

  public getAll() {
    return this._authHttp
    .get(`${this._url}`, { headers: contentHeaders} )
    .map((res: Response) => res.json())
    .map(data => {
      return data.map(contact => {
        return new Contact(
          contact._id,
          contact.email,
          contact.name,
          contact.city,
          contact.phoneNumber,
          contact.company,
          contact.createdAt
        )
      });
    })
    .subscribe((contacts: Array<Contact>) => {
      this._dataStore.contacts = contacts;
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

We use our custom build AuthHttp service to load data from our Express application. When a response is received, we transform it into a JSON file, and after that, we just instantiate a new contact for each entity from the dataset.

Instead of returning the whole Observable from the HTTP service, we use our internal data store to persist all the contacts. After we have successfully updated the data store with the new data, we push the changes to our contactsObserver.

Any component that is subscribed to our stream of contacts will get the new values from the Observable data stream. In this way, we always keep our components synced using one single point of entry.

Much of our public method's logic is the same, but we still have a few distinct elements, for example, the update method:

  public update(contact: Contact) {
    return this._authHttp
    .put(
      `${this._url}/${contact._id}`,
      this._serialize(contact),
      { headers: contentHeaders}
    )
    .map((res: Response) => res.json())
    .map(data => {
      return new Contact(
        data._id,
        data.email,
        data.name,
        data.city,
        data.phoneNumber,
        contact.company,
        data.createdAt
      )
    })
    .subscribe((contact: Contact) => {
      // update the current list of contacts
      this._dataStore.contacts.map((c, i) => {
        if (c._id === contact._id) {
          this._dataStore.contacts[i] = contact;
        }
      });
      // update the current contact
      this._dataStore.contact = contact;
      this._contactObserver.next(this._dataStore.contact);
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

The update method is almost the same as the create() method, however it takes the contact's ID as the URL param. Instead of pushing new values down a data stream, we return the Observable from the Http service, in order to apply operations from the caller module.

Now, if we would like to make changes directly on the datastore and push the new values through the contacts data stream, we could showcase this in the remove contact method:

  public remove(contactId: string) {
    this._authHttp
    .delete(`${this._url}/${contactId}`)
    .subscribe(() => {
      this._dataStore.contacts.map((c, i) => {
        if (c._id === contactId) {
          this._dataStore.contacts.splice(i, 1);
        }
      });
      this._contactsObserver.next(this._dataStore.contacts);
    }, err => console.error(err));
  }

We simply use the map() function to find the contact we deleted and remove it from the internal store. Afterwards, we send new data to the subscribers.

Contact component

As we have moved everything related to the contact domain, we can define a main component in our module. Let's call it contact-manager/public/src/contact/contact.component.ts. Add the following lines of code:

import { Component } from 'angular2/core';
import { RouteConfig, RouterOutlet } from 'angular2/router';
import { ContactListComponent } from './contact-list.component';
import { ContactCreateComponent } from './contact-create.component';
import { ContactEditComponent } from './contact-edit.component';

@RouteConfig([
  { path: '/', as: 'ContactList', component: ContactListComponent, useAsDefault: true },
  { path: '/:id', as: 'ContactEdit', component: ContactEditComponent },
  { path: '/create', as: 'ContactCreate', component: ContactCreateComponent }
])
@Component({
    selector: 'contact',
    directives: [
      ContactListComponent,
      RouterOutlet
    ],
    template: `
      <router-outlet></router-outlet>
    `
})
export class ContactComponent {
  constructor() {}
}

Our component has no logic associated with it, but we used the RouterConfig annotation. The route config decorator takes an array of routes. Each path specified in the config will match the browser's URL. Each route will load the mounted component. In order to reference routes in the template, we need to give them a name.

Now, the most appealing part is that we can take this component with the configured routes and mount it on another component to have Child/Parent routes. In this case, it becomes nested routing, which is a very powerful feature added to Angular 2.

Our application's routes will have a tree-like structure; other components load components with their configured routes. I was pretty amazed by this feature because it enables us to truly modularize our application and create amazing, reusable modules.

List contacts component

In the previous component, we used three different components and mounted them on different routes. We are not going to discuss each of them, so we will choose one. As we have already worked with forms in the Signin component, let's try something different and implement the list contacts functionality.

Create a new file called contact-manager/public/src/contact/contact-list.component.ts and add the following code for your component:

import { Component, OnInit } from 'angular2/core';
import { RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-list',
    directives: [RouterLink],
    template: `
      <div class="row">
        <h4>
          Total contacts: <span class="muted">({{contacts.length}})</span>
          <a href="#" [routerLink]="['ContactCreate']">add new</a>
        </h4>
        <div class="contact-list">
          <div class="card-item col col-25 contact-item"
            *ngFor="#contact of contacts">
            <img src="{{ contact.image }}" />
            <h3>
              <a href="#" [routerLink]="['ContactEdit', { id: contact._id }]">
                {{ contact.name }}
              </a>
            </h3>
            <p>
              <span>{{ contact.city }}</span>
              <span>·</span>
              <span>{{ contact.company }}</span>
            </p>
            <p><span>{{ contact.email }}</span></p>
            <p><span>{{ contact.phoneNumber }}</span></p>
          </div>
        </div>
      </div>
    `
})
export class ContactListComponent implements OnInit {
  public contacts: Array<Contact> = [];
  private _contactService: ContactService;

  constructor(contactService: ContactService) {
    this._contactService = contactService;
  }

  ngOnInit() {
    this._contactService.contacts.subscribe(contacts => {
      this.contacts = contacts;
    });
    this._contactService.getAll();
  }
}

In our component's ngOnInit(), we subscribe to the contacts data stream. Afterwards, we retrieve all the contacts from the backend. In the template, we use ngFor to iterate over the dataset and display each contact.

Creating a contact component

Now that we can list contacts in our application, we should also be able to add new entries. Remember that earlier we used the RouterLink to be able to navigate to the CreateContact route.

The preceding route will load the CreateContactComponent, which will enable us to add new contact entries into our database, through the Express API. Let's create a new component file public/src/contact/components/contact-create.component.ts:

import { Component, OnInit } from 'angular2/core';
import { Router, RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-create,
    directives: [RouterLink],
    templateUrl: 'src/contact/components/contact-form.html'
})
export class ContactCreateComponent implements OnInit {
  public contact: Contact;
  private _router: Router;
  private _contactService: ContactService;

  constructor(
    contactService: ContactService,
    router: Router
  ) {
    this._contactService = contactService;
    this._router = router;
  }

  ngOnInit() {
    this.contact = new Contact();
  }

  onSubmit(event) {
    event.preventDefault();

    this._contactService
    .create(this.contact)
    .subscribe((contact) => {
      this._router.navigate(['ContactList']);
    }, err => console.error(err));
  }
}

Instead of using an embedded template, we are using an external template file that is configured using the templateUrl property in the component annotation. There are pros and cons for each situation. The benefits of using an external template file would be that you can reuse the same file for more than one component.

The downfall, at the moment of writing the book, in Angular 2 is that it's hard to use relative paths to your template files, so this would make your components less portable. Also I like to keep my templates short, so they can fit easily inside the component, so in most cases I'll probably use embedded templates.

Let's take a look at the template before further discussing the component, public/src/contact/components/contact-form.html:

<div class="row contact-form-wrapper">
  <a href="#" [routerLink]="['ContactList']">&lt; back to contacts</a>
  <h2>Add new contact</h2>
  <form role="form"
    (submit)="onSubmit($event)">

    <div class="form-group">
      <label for="name">Full name</label>
      <input type="text" [(ngModel)]="contact.name"
        class="form-control" id="name" placeholder="Jane Doe">
    </div>
    <div class="form-group">
      <label for="email">E-mail</label>
      <input type="text" [(ngModel)]="contact.email"
        class="form-control" id="email" placeholder="jane.doe@example.com">
    </div>
    <div class="form-group">
      <label for="city">City</label>
      <input type="text"
        [(ngModel)]="contact.city"
        class="form-control" id="city" placeholder="a nice place ...">
    </div>
    <div class="form-group">
      <label for="company">Company</label>
      <input type="text"
        [(ngModel)]="contact.company"
        class="form-control" id="company" placeholder="working at ...">
    </div>
    <div class="form-group">
      <label for="phoneNumber">Phone</label>
      <input type="text"
        [(ngModel)]="contact.phoneNumber"
        class="form-control" id="phoneNumber" placeholder="mobile or landline">
    </div>

    <button type="submit" class="button">Submit</button>
  </form>
</div>

In the template we are using a onSubmit() method from the component to piggyback the form submission and in this case create a new contact and store the data in MongoDB. When we successfully create the contact we want to navigate to the ContactList route.

We are not using local variables, instead we are using two-way data binding with the ngModel for each input, mapped to the properties of the contact object. Now, each time the user changes the inputs value, this is stored in the contact object and on submit it's sent across the wire to the backend.

The RouterLink is used to construct the navigation to the ContactList component from the template. I've left a small improvement, the view title will be the same both for creating and editing, more precisely "Add new contact", and I'll let you figure it out.

Editing an existing contact

When editing a contact, we want to load a specific resource by ID from the backend API and make changes for that contact. Lucky for us this is quite simple to achieve in Angular. Create a new file public/src/contact/components/contact-edit.component.ts:

import { Component, OnInit } from 'angular2/core';
import { RouteParams, RouterLink } from 'angular2/router';
import { ContactService } from '../contact.service';
import { Contact } from '../contact';

@Component({
    selector: 'contact-edit',
    directives: [RouterLink],
    templateUrl: 'src/contact/components/contact-form.html'
})
export class ContactEditComponent implements OnInit {
  public contact: Contact;
  private _contactService: ContactService;
  private _routeParams: RouteParams;

  constructor(
    contactService: ContactService,
    routerParams: RouteParams
  ) {
    this._contactService = contactService;
    this._routeParams = routerParams;
  }

  ngOnInit() {
    const id: string = this._routeParams.get('id');
    this.contact = new Contact();
    this._contactService
    .contact.subscribe((contact) => {
      this.contact = contact;
    });
    this._contactService.getOne(id);
  }

  onSubmit(event) {
    event.preventDefault();

    this._contactService
    .update(this.contact)
    .subscribe((contact) => {
      this.contact = contact;
    }, err => console.error(err));
  }
}

We are not so far away from the ContactCreateComponent, the structure of the class is almost the same. Instead of the Router, we are using RouteParams to load the ID from the URL and retrieve the desired contact from the Express application.

We subscribe to the contact Observable returned by the ContactService to get the new data. In other words our component will react to the data stream and when the data is available it will display it to the user.

When submitting the form, we update the contact persisted in MongoDB and change the view's contact object with the freshly received data from the backend.

 

Finishing touch


We have added all the necessary modules into our application. We should also take a final look at our main app component, found under the following path—contact-manager/public/src/app.component.ts:

import { Component } from 'angular2/core';
import { RouteConfig, RouterOutlet } from 'angular2/router';
import { Router } from 'angular2/router';
import { AuthHttp } from './auth/auth-http';
import { Signin } from './auth/signin';
import { Register } from './auth/register';
import { ContactComponent } from './contact/components/contact.component';

@RouteConfig([
  { path: '/signin', as: 'Signin', component: Signin },
  { path: '/register', as: 'Register', component: Register },
  { path: '/contacts/...', as: 'Contacts', component: ContactComponent, useAsDefault: true }
])
@Component({
    selector: 'cm-app',
    directives: [
      Signin,
      Register,
      ContactComponent,
      RouterOutlet
    ],
    template: `
      <div class="app-wrapper col card whiteframe-z2">
        <div class="row">
          <h3>Contact manager</h3>
        </div>
        <router-outlet></router-outlet>
      </div>
    `
})
export class AppComponent {
  private _authHttp: AuthHttp;
  private _router: Router;

  constructor(authHttp: AuthHttp, router: Router) {
    this._authHttp = authHttp;
    this._router = router;
    this._authHttp.unauthorized.subscribe((res) => {
      if (res) {
        this._router.navigate(['./Signin']);
      }
    });
  }
}

We mount all the components to their specific routes. Also, when we mount the Contact component, we'll bring in all the configured routes from the component.

In order to be notified when a request is unauthorized, we subscribe to the AuthHttp service's unauthorized data stream. If a request needs authentication, we redirect the user to the sign-in page.

The boot file for our application will look something like this:

import { bootstrap } from 'angular2/platform/browser';
import { provide } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import { ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy } from 'angular2/router';
import { AuthHttp } from './auth/auth-http';
import { AuthService } from './auth/auth.service';
import { ContactService } from './contact/contact.service';
import { AppComponent } from './app.component';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

bootstrap(AppComponent, [
  ROUTER_PROVIDERS,
  HTTP_PROVIDERS,
  AuthService,
  AuthHttp,
  ContactService,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
]);

We import and define the necessary providers and also add the operators we used from RxJs. This is because Angular, by default, uses only a stripped-down version of the Observable module.

Through the contact module we used a custom class named Contact, which plays the role of a Contact model. This will be instantiated any time we want to make sure we are working with a contact entity. Besides, the nice thing about TypeScript is that it enables us to use structured code.

Classes come in handy when we want to have initial values, for example, in our components we used a contact.image property to display a contact's profile image. This was not implemented in the backend, so we use a mock URL for an image. Let's see the Contact class, public/src/contact/contact.ts:

export class Contact {
  _id: string;
  email: string;
  name: string;
  city: string;
  phoneNumber: string;
  company: string;
  image: string;
  createdAt: string;

  constructor(
    _id?: string,
    email?: string,
    name?: string,
    city?: string,
    phoneNumber?: string,
    company?: string,
    createdAt?: string
  ) {
    this._id = _id;
    this.email = email;
    this.name = name;
    this.city = city;
    this.phoneNumber = phoneNumber;
    this.company = company;
    this.image = 'http://placehold.it/171x100';
    this.createdAt = createdAt;
  }
}

As you can see we just define what properties a contact instance can have and create a default value for the image property. Arguments passed to the constructor marked with ? are optional.

At this moment, we should have everything in place; in case you missed something, you can check out the final version of the code.

The key takeaways from this chapter are as follows:

  • Building backend web services using Node.js, Express, and MongoDB

  • Writing tests first, before actually implementing functionalities

  • Securing our API routes using Passport

  • Making Angular 2 and Express communicate and work together

  • Getting into Reactive Extensions and reactive programming

  • Building a custom Angular HTTP service

 

Summary


This brings us to the end of this rather introductory chapter.

We went full stack, right from implementing our backend logic to learning to write tests before actual implementations. We exposed a RESTful route for our resources from MongoDB. We also built a small Angular 2 frontend application that interacts with the web server.

In the next chapter, we'll dive deeper into MongoDB and start working with monetary data. It should be a fun ride!

About the Author
  • Robert Onodi

    Robert Onodi has been developing software for close to a decade now. He started working on small applications in Flash, and later moved on to the LAMP stack. For most of his career, he has been working with JavaScript. Having knowledge of both server-side technologies and a passion for JavaScript, he rapidly adopted Node.js in his development stack. Currently, he works at Evozon, where he shares his dedication for technology and JavaScript with others and develops modern applications using the MEAN stack. Besides his daily programming routine, he is also passionate about mentoring and training.

    Browse publications by this author
Latest Reviews (6 reviews total)
It was awesome. Loved it really
Awesome. Just it needs to be revised.
The book covers the basic and goes into details with ease. I am a novice on javascript, but I am able to follow the book. Overall, a very well structured book.
libro molto chiaro e "diretto!