Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

MEAN App: Server

Part 1: REST API using Express Server written in TypeScript with Mongoose plus Mocha and Chai tests.

Series

This post is part of a series on building a MEAN app using TypeScript with Angular Material and Reactive programming.

Source Code

You can download the source code and follow along or fork the repository on GitHub:

First, run the gulp tasks, then start the Node.js Express server.

$ gulp
$ chmod +x ./dist/bin/www
$ ./dist/bin/www

Then, serve the Angular client using the CLI:

$ ng serve

Goals

Our goals for this series are:

  1. Create a simple CRUD app similar to the Tour of Heros tutorial app for Angular.
  2. Create the REST API using Express written in TypeScript with Mongoose for persisting data to MongoDb.
  3. Use Angular Material and Angular Flex Layout for the UI.
  4. Use Reactive Extensions for JavaScript, specifically, RxJS and ngrx.

In this post we will start by building the REST API using Express written in TypeScript.

Project Structure

Our project will have a client and a server folder. For now, let’s focus on the server side.

├── client
├── dist
├── gulpfile.js
├── gulpfile.ts
├── package.json
└── server
    ├── bin
    │   └── www
    ├── src
    │   ├── api
    │   │   └── heros.ts
    │   ├── interfaces
    │   │   └── hero.ts
    │   ├── models
    │   │   └── hero.ts
    │   ├── schemas
    │   │   └── hero.ts
    │   ├── server.ts
    │   └── tests
    │       └── hero.ts
    └── tsconfig.json

Install Node and npm

To get started we will create a new project using the Node Package Manager (npm).

If you have not installed Node, which includes npm, I would suggest you use Homebrew:

</code>$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update
$ brew doctor</pre>

Then, install node using the `brew install` command:

```bash
$ brew install node

Next, create a new project using npm. I am going to call the project “mean-material-reactive”:

$ mkdir mean-material-reactive
$ cd mean-material-reactive
$ npm init

Follow the prompts in order to create a new project. At this point your project should contain a single package.json file.

Install Gulp

The next step is to install Gulp, which we will be using to automate our workflow. We’ll also install several plugins that we will need:

$ npm install gulp-cli -g
$ npm install gulp --save-dev
$ npm install typescript --save-dev
$ npm install del --save-dev
$ npm install gulp-sourcemaps --save-dev
$ npm install gulp-typescript --save-dev
$ npm install run-sequence --save-dev
$ npm install ts-node --save-dev
$ touch gulpfile.js
$ touch gulpfile.ts

Note the use of the --save-dev flag, which instructs npm to write the development dependencies into our project’s package.json file.

Here is a quick rundown of the plugins that we are using:

Also, note that I have created two empty files; the gulpfile.js file will use ts-node to execute the gulpfile.ts TypeScript file.

Here is what the gulpfile.js file will contain:

require('ts-node').register({
  project: false,
  disableWarnings: true,
});
require('./gulpfile.ts');

Let’s start building the gulpfile.ts file. We will start by created two tasks:

  1. clean - will delete our distributable directories.
  2. build:express - will build our Express HTTP server.

We will also create a default task that will use the run-sequence plugin to execute a series of Gulp tasks.

This is just the start of our gulp tasks. We will be building on this more as we build our application.

const gulp = require('gulp'),
  del = require('del'),
  runSequence = require('run-sequence'),
  sourceMaps = require('gulp-sourcemaps'),
  tsc = require('gulp-typescript');

/**
 * Remove dist directory.
 */
gulp.task('clean', (done) => {
  return del(['dist'], done);
});

/**
 * Copy start script.
 */
gulp.task('copy', () => {
  return gulp.src('server/bin/*').pipe(gulp.dest('dist/bin'));
});

/**
 * Build the server.
 */
gulp.task('build:express', () => {
  const project = tsc.createProject('server/tsconfig.json');
  const result = gulp
    .src('server/src/**/*.ts')
    .pipe(sourceMaps.init())
    .pipe(project());
  return result.js.pipe(sourceMaps.write()).pipe(gulp.dest('dist/server'));
});

/**
 * Build the project.
 */
gulp.task('default', (done) => {
  runSequence('clean', 'copy', 'build:express');
});

If you attempt to execute gulp in the project directory you should receive an error. This is because we have not yet installed Express and configured the server code via the tsconfig.json file. Let’s do that.

Install Express

Next, let’s install Express:

$ npm install express --save

Note the --save flag. This will save a production dependency in the package.json file.

Next, let’s set up our server folder structure:

$ mkdir server
$ cd server
$ touch tsconfig.json

Then, modify the tsconfig.json file that will contain the server’s TypeScript configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true,
    "typeRoots": [
      "../node_modules/@types/"
    ]
  },
  "compileOnSave": true,
  "exclude": [
    "node_modules/*"
  ]
}

If you are not familiar with the TypeScript compiler options and using the tsconfig.json file, check out the documentation on tsconfig.json.

Server Start Script

Next we need to create our Express HTTP server start script:

$ mkdir src
$ mkdir bin
$ cd src/bin
$ touch www

Here is the full contents of the www file:

#!/usr/bin/env node
'use strict';

//module dependencies
var server = require('../server/server');
var debug = require('debug')('express:server');
var http = require('http');

//create http server
var httpPort = normalizePort(process.env.PORT || 8080);
var app = server.Server.bootstrap().app;
app.set('port', httpPort);
var httpServer = http.createServer(app);

//listen on provided ports
httpServer.listen(httpPort);

//add error handler
httpServer.on('error', onError);

//start listening on port
httpServer.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */
function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */
function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */
function onListening() {
  var addr = httpServer.address();
  var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

This start script was slightly modified by an example on the Google Cloud Platform developer website, so I take not credit for it. This will create our HTTP server on the default port of 8080. You can customize this to run on the standard HTTP port 80 in production.

Next, we need to modify the permissions on the www file so that we can execute it:

$ chmod +x www

Install Middleware

The first step of using Express is to install and configure all of the middleware that our application needs. Let’s install these via npm:

$ npm install body-parser --save
$ npm install morgan --save
$ npm install errorhandler --save

Then, we need to install the TypeScript declaration files for each:

$ npm install @types/body-parser --save-dev
$ npm install @types/morgan --save-dev
$ npm install @types/errorhandler --save-dev

Here’s a quick rundown of each of these and what they do:

Server Class

Let’s start building out the Server class:

import * as bodyParser from "body-parser";
import * as express from "express";
import * as morgan from "morgan";
import * as path from "path";
import errorHandler = require("errorhandler");
import mongoose = require("mongoose");

/**
 * The server.
 *
 * @class Server
 */
export class Server {

  /**
   * The express application.
   * @type {Application}
   */
  public app: express.Application;

  /**
   * Bootstrap the application.
   * @static
   */
  public static bootstrap(): Server {
    return new Server();
  }

  /**
   * @constructor
   */
  constructor() {
    //create expressjs application
    this.app = express();

    //configure application
    this.config();

    //add api
    this.api();
  }

  /**
   * Create REST API routes
   *
   * @class Server
   */
  public api() {
    //empty for now
  }

  /**
   * Configure application
   *
   * @class Server
   */
  public config() {
    // morgan middleware to log HTTP requests
    this.app.use(morgan("dev"));

    //use json form parser middlware
    this.app.use(bodyParser.json());

    //use query string parser middlware
    this.app.use(bodyParser.urlencoded({
      extended: true
    }));

    // connect to mongoose
    mongoose.connect("mongodb://localhost:27017/mean-material-reactive");
    mongoose.connection.on("error", error => {
      console.error(error);
    });

    //catch 404 and forward to error handler
    this.app.use(function(err: any, req: express.Request, res: express.Response, next: express.NextFunction) {
        err.status = 404;
        next(err);
    });

    //error handling
    this.app.use(errorHandler());
  }
}

Some things to note:

Install MongoDB and Mongoose

The next step is to install MongoDB. We’ll be using Homebrew to do this (we already installed Homebrew above in order to install Node).

$ brew install mongodb
$ ln -sfv /usr/local/opt/mongodb/*.plist ~/Library/LaunchAgents

The second command above will enable MongoDb to start when your system starts. This is handy for your development enviroment.

Now, install Mongoose using npm:

$ npm install mongoose --save
$ npm install @types/mongoose --save-dev
$ npm install @types/mongodb --save-dev

Note that I have also installed the TypeScript declaration files for both mongoose as well as MongoDb.

Define Interface

The next step is to define our application’s interfaces. For this tutorial I am going to have a single Hero interface.

First, create the server/src/interfaces directory along with a hero.ts file:

$ mkdir interfaces
$ cd interfaces
$ touch hero.ts

Here is the very simply interface for our Hero:

export interface Hero {
  name?: string;
}

Our Hero only has a single string property: name.

Define Schema

The next step is to define a schema for our model. Create a new server/src/schemas directory along with a hero.ts file:

$ mkdir schemas
$ cd schemas
$ touch hero.ts

Then, define the schema in hero.ts:

import { Schema } from 'mongoose';

export var heroSchema: Schema = new Schema({
  createdAt: { type: Date, default: Date.now },
  name: String,
});

You might note that I also included an additional property named createdAt in my schema. This is going to be a Date that tracks when the hero was created, as the default value is Date.now.

Define Model

Finally, create the model that is based on our interface and schema. Create a new server/src/models directory along with the hero.ts file:

$ mkdir models
$ cd models
$ touch hero.ts

Now, let’s define our model:

import mongoose = require("mongoose");
import { Document, Model } from "mongoose";
import { Hero as HeroInterface } from "../interfaces/hero";
import { heroSchema } from "../schemas/hero";

export interface HeroModel extends HeroInterface, Document {}

export interface HeroModelStatic extends Model<HeroModel> {}

export const Hero = mongoose.model<HeroModel, HeroModelStatic>("Hero", heroSchema);

Great! We have now defined our Hero interface and the associated schema and model for interfacing with MongoDb using Mongoose.

REST API

The next step is to define a REST API in our Express application. For this tutorial we will only be defining a single REST API endpoint for our heros. Let’s start by creating the server/src/api directory and a new heros.ts file:

$ mkdir api
$ cd api
$ touch heros.ts

Now, create a new HerosApi class that will handle our CRUD operations using REST:

// express
import { NextFunction, Response, Request, Router } from "express";

// model
import { Hero } from "../models/hero";

/**
 * @class HerosApi
 */
export class HerosApi {

  /**
   * Create the api.
   * @static
   */
  public static create(router: Router) {
    // DELETE
    router.delete("/heros/:id([0-9a-f]{24})", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().delete(req, res, next);
    });

    // GET
    router.get("/heros", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().list(req, res, next);
    });
    router.get("/heros/:id([0-9a-f]{24})", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().get(req, res, next);
    });

    // POST
    router.post("/heros", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().create(req, res, next);
    });

    // PUT
    router.put("/heros/:id([0-9a-f]{24})", (req: Request, res: Response, next: NextFunction) => {
      new HerosApi().update(req, res, next);
    });
  }

  /**
   * Create a new hero.
   * @param req {Request} The express request object.
   * @param res {Response} The express response object.
   * @param next {NextFunction} The next function to continue.
   */
  public create(req: Request, res: Response, next: NextFunction) {
    // create hero
    const hero = new Hero(req.body);
    hero.save().then(hero => {
      res.json(hero.toObject());
      next();
    }).catch(next);
  }

  /**
   * Delete a hero.
   * @param req {Request} The express request object.
   * @param res {Response} The express response object.
   * @param next {NextFunction} The next function to continue.
   */
  public delete(req: Request, res: Response, next: NextFunction) {
    // verify the id parameter exists
    const PARAM_ID: string = "id";
    if (req.params[PARAM_ID] === undefined) {
      res.sendStatus(404);
      next();
      return;
    }

    // get id
    const id: string = req.params[PARAM_ID];

      // get hero
      Hero.findById(id).then(hero => {

      // verify hero exists
      if (hero === null) {
        res.sendStatus(404);
        next();
        return;
      }

      hero.remove().then(() => {
        res.sendStatus(200);
        next();
      }).catch(next);
    }).catch(next);
  }

  /**
   * Get a hero.
   * @param req {Request} The express request object.
   * @param res {Response} The express response object.
   * @param next {NextFunction} The next function to continue.
   */
  public get(req: Request, res: Response, next: NextFunction) {
    // verify the id parameter exists
    const PARAM_ID: string = "id";
    if (req.params[PARAM_ID] === undefined) {
      res.sendStatus(404);
      next();
      return;
    }

    // get id
    const id: string = req.params[PARAM_ID];

    // get hero
      Hero.findById(id).then(hero => {

      // verify hero was found
      if (hero === null) {
        res.sendStatus(404);
        next();
        return;
      }

      // send json of hero object
      res.json(hero.toObject());
      next();
    }).catch(next);
  }

  /**
   * List all heros.
   * @param req {Request} The express request object.
   * @param res {Response} The express response object.
   * @param next {NextFunction} The next function to continue.
   */
  public list(req: Request, res: Response, next: NextFunction) {
    // get heros
    Hero.find().then(heros => {
      res.json(heros.map(hero => hero.toObject()));
      next();
    }).catch(next);
  }

  /**
   * Update a hero.
   * @param req {Request} The express request object.
   * @param res {Response} The express response object.
   * @param next {NextFunction} The next function to continue.
   */
  public update(req: Request, res: Response, next: NextFunction) {
    const PARAM_ID: string = "id";

    // verify the id parameter exists
    if (req.params[PARAM_ID] === undefined) {
      res.sendStatus(404);
      next();
      return;
    }

    // get id
    const id: string = req.params[PARAM_ID];

    // get hero
    Hero.findById(id).then(hero => {

      // verify hero was found
      if (hero === null) {
        res.sendStatus(404);
        next();
        return;
      }

      // save hero
      Object.assign(hero, req.body).save().then((hero: HeroModel) => {
        res.json(hero.toObject());
        next();
      }).catch(next);
    }).catch(next);
  }

}

Some things to note:

Before we go any further we need to allow cross-origin requests. Our server (Express) and client (Angular) will be running on the same local machine, but on different ports (one on port 8080 and the other on Angular CLI’s default port of 4200), so we need to enable CORS. To do this, let’s use and install the cors middleware:

$ npm install cors --save
$ npm install @types/cors --save-dev

Then, import the cors middleware into the Server class in server/src/server.ts:

import * as cors from 'cors';

Next, import the HerosApi class into our Server class.

import { HerosApi } from './api/heros';

If you recall, we left the api() method in our Server class empty. Now it’s finally time to implement this and to wire up our HerosApi:

export class Server {

  // code omitted

  /**
   * REST API endpoints.
   */
  public api() {
    var router = express.Router();

    // configure CORS
    const corsOptions: cors.CorsOptions = {
      allowedHeaders: ["Origin", "X-Requested-With", "Content-Type", "Accept", "X-Access-Token"],
      credentials: true,
      methods: "GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE",
      origin: "http://localhost:4200",
      preflightContinue: false
    };
    router.use(cors(corsOptions));

    // root request
    router.get("/", (req: express.Request, res: express.Response, next: express.NextFunction) => {
      res.json({ announcement: "Welcome to our API." });
      next();
    });

    // create API routes
    HerosApi.create(router);

    // wire up the REST API
    this.app.use("/api", router);

    // enable CORS pre-flight
    router.options("*", cors(corsOptions));
  }

}

A couple of things to note:

Test REST API

We’ll be using a testing framework called Mocha, along with Chai for BSD style assertions, and the chai-http plugin. If you haven’t caught on already, I’m a huge TypeScript fan. So, we’ll also be using mocha-typescript to write our tests using TypeScript, and leveraging some ES6/ES2015 features like decorators.

First, let’s get things installed via npm:

$ npm install mocha --save-dev
$ npm install chai --save-dev
$ npm install chai-http --save-dev
$ npm install mocha-typescript --save-dev
$ npm install @types/mocha --save-dev
$ npm install @types/chai --save-dev
$ npm install @types/chai-http --save-dev

Let’s create a server/src/tests directory, and a heros.ts test file:

$ mkdir tests
$ cd tests
$ touch heros.ts

Let’s start building our test:

process.env.NODE_ENV = "test";

// mocha
import "mocha";
import { suite, test } from "mocha-typescript";

// mongodb
import { ObjectID } from "mongodb";

// server
import { Server } from "../server";

// model
import { Hero } from "../interfaces/hero";
import { HeroModel, HeroModelStatic } from "../models/hero";
import { heroSchema } from "../schemas/hero";

// mongoose
import mongoose = require("mongoose");

//require http server
var http = require("http");

//require chai and use should assertions
let chai = require("chai");
chai.should();

//configure chai-http
chai.use(require("chai-http"));

Here is what we are doing at the top of the hero.ts test file:

Now that we have the imports out of the way, let’s start to define the HerosTest class:

@suite class HerosTest {

  // constants
  public static BASE_URI: string = "/api/heros";

  // the mongooose connection
  public static connection: mongoose.Connection;

  // hero model
  public static Hero: HeroModelStatic;

  // hero document
  public static hero: HeroModel;

  // the http server
  public static server: any;

  /**
   * Before all hook.
   */
  public static before() {
     // connect to MongoDB
    mongoose.connect("mongodb://localhost:27017/mean-material-reactive");
    HerosTest.Hero = mongoose.model<HeroModel, HeroModelStatic>("Hero", heroSchema);

    // create http server
    let port = 8001;
    let app = Server.bootstrap().app;
    app.set("port", port);
    HerosTest.server = http.createServer(app);
    HerosTest.server.listen(port);

    return HerosTest.createHero();
  }

  /**
   * After all hook
   */
  public static after() {
    return HerosTest.hero.remove()
    .then(() => {
      return mongoose.disconnect();
    });
  }

  /**
   * Create a test hero.
   */
  public static createHero(): Promise<HeroModel> {
    const data: Hero = {
      name: "Brian Love"
    };
    return new HerosTest.Hero(data).save().then(hero => {
      HerosTest.hero = hero;
    });
  }

}

Our HerosTest class is starting to come together.

Now that we have the bulk of the test setup and configured, let’s starting adding tests:

@suite class HerosTest {

  // code omitted

  @test public delete() {
    const data: Hero = {
      name: "To be deleted"
    };
    return new HerosTest.Hero(data).save().then(hero => {
      return chai.request(HerosTest.server).del(`${HerosTest.BASE_URI}/${hero._id}`).then(response => {
        response.should.have.status(200);
      });
    });
  }

  @test public get() {
    return chai.request(HerosTest.server).get(`${HerosTest.BASE_URI}/${HerosTest.hero._id}`).then(response => {
      response.should.have.status(200);
      response.body.should.be.a("object");
      response.body.should.have.property("name").eql(HerosTest.hero.name);
    });
  }

  @test public list() {
    return chai.request(HerosTest.server).get(HerosTest.BASE_URI).then(response => {
      response.should.have.status(200);
      response.body.should.be.an("array");
      response.body.should.have.lengthOf(1);
    });
  }

  @test public post() {
    const data: Hero = {
      name: "Magneto"
    };
    return chai.request(HerosTest.server).post(HerosTest.BASE_URI)
    .send(data)
    .then(response => {
      response.should.have.status(200);
      response.body.should.be.a("object");
      response.body.should.have.a.property("_id");
      response.body.should.have.property("name").eql(data.name);
      return HerosTest.Hero.findByIdAndRemove(response.body._id).exec();
    });
  }

  @test public put() {
    const data: Hero = {
      name: "Superman"
    }
    return chai.request(HerosTest.server).put(`${HerosTest.BASE_URI}/${HerosTest.hero._id}`)
    .send(data)
    .then(response => {
      response.should.have.status(200);
      response.body.should.be.a("object");
      response.body.should.have.a.property("_id");
      response.body.should.have.property("name").eql(data.name);
    });
  }

}

Let’s review our tests:

Let’s go ahead and run our tests to ensure that our REST API is working and operational.

Before we can run our tests, we need to create a new task in our gulpfile.ts. But, before we do that, let’s install the gulp-mocha package:

$ npm install gulp-mocha --save-dev

Now, let’s add a new task to the gulpfile.ts:

gulp.task('test:express', () => {
  gulp.src('dist/server/tests', { read: false }).pipe(gulpMocha());
});

Now, add the test:express task to the sequence of tasks to execute in the default task:

gulp.task('default', (done) => {
  runSequence('clean', 'copy', 'build:express', 'test:express');
});

At this point we are ready to run gulp in our project:

$ gulp

You should see a success message indicating that all four tests have passed. Sweet!

MEAN App Server Tests

Review

Just to review, we:

Continue the Series

I hope you have enjoyed the first part in the series on building a MEAN app using TypeScript with Angular Material and Reactive programming.

Next, in part two of the series, we’ll start building our Angular application using Angular Material.