Brian Love
Angular + TypeScript Developer in Denver, CO

TypeScript 2 + Express + Node.js

Reading time ~19 minutes

TypeScript 2 enables you to develop solid web applications using Express on Node.js

I previously wrote an article on getting started with TypeScript (1.x) with Express and Node.js. This post is very similar, but will take advantage of the new features of TypeScript 2.

Ok, let’s get started.

Source Code

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

Getting Started

To get started we will be using the node package manager (npm) to install the dependencies for our application. Npm is installed along with Node.js. If you have not installed Node.js you can do so via homebrew.

Install Homebrew and update it:

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

Then, install node using the brew install command:

$ brew install node

Create Project

Next, let’s create a new project using the npm init command. I am going to name the project heros in honor of Angular. :)

$ mkdir heroes
$ cd heros
$ npm init

After answering the prompts you will have a new package.json file in your project folder. Let’s add some custom scripts as well.

First, add the dev script. This will use the nodemon module to watch for any changes to the source files of the express web application. If a file changes, then we will restart the server.

Next, add the grunt script. This simply invokes the grunt task runner. We will install this later in this tutorial.

Finally, add the start script. This will use node to execute a bin/www file.

If you are using Linux or a Mac your package.json file should look like:

{
  "name": "heros",
  "description": "The tour of heros",
  "version": "1.0.0",
  "private": true,
  "author": "Brian Love",
  "scripts": {
    "dev": "NODE_ENV=development nodemon ./bin/www",
    "grunt": "grunt",
    "start": "node ./bin/www"
  }
}

If you are using Windows your package.json file should look like:

{
  "name": "heros",
  "description": "The tour of heros",
  "version": "1.0.0",
  "private": true,
  "author": "Brian Love",
  "scripts": {
    "dev": "SET NODE_ENV=development && nodemon ./bin/www",
    "grunt": "grunt",
    "start": "node ./bin/www"
  }
}

Note the minor change to the dev script for Windows users.

Install Express

The next step is to install the Express dependency. I am including the --save flag to my npm install command so that the dependency is saved in the package.json file.

$ npm install express --save

Note that this also generates a new node_modules folder in your project. If you are using Git then should add this folder to your .gitignore file.

Start script

Next we need to create our start script. If you recall, we specified a start property within the scripts configuration in the package.json file. I set the value of this to: “node ./bin/www”. So, let’s create an empty file at: bin/www

$ mkdir bin
$ cd bin
$ touch www

Here is the full contents of the www file:

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

//module dependencies
var server = require("../dist/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 is a bit long. So let me break this down and explain each section.

Lines 1-7

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

//module dependencies
var server = require("../dist/server");
var debug = require("debug")("express:server");
var http = require("http");
  • First we have the node shebang to execute this script. If you are using Windows, just rename this file to www.js and node will execute this based on the file extension (I think so anyways).
  • Then we are enabling strict mode via the "use strict" command.
  • And then I am requiring some dependencies. First, I will have a module (file) at: dist/server.js. We have not created this yet, so don’t worry. Then we require the express and http modules.

Lines 10-14

//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);
  • First I determine the port that we will bind the http server to, and listen on. This will first check for a PORT enviroment variable and then default to 8080.
  • I am also using a normalizePort() function that was provided by the Google Cloud Platform team. I take no credit for this, or for the onError() and onListening() code. I borrowed these from their example application.
  • Next, I am going to bootstrap() my application. This will make more sense after we have created our Server class.
  • I then set the port for our HTTP server.
  • And finally we create the http server, passing in our express app.

Lines 17-23

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

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

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

In this section I am specifying the port that our http server will listen on and then I attach some event handlers. I am listening to the error and listening events. The error event will fire when an error occurs during the creation of the application. And the listening event will fire when the http server has started and is listening on the specified port.

Install TypeScript and Grunt

Next, install TypeScript using the npm install command:

$ npm install typescript --save-dev

I am going to be using the Grunt task runner to compile the TypeScript source code. Use npm to install grunt:

$ npm install grunt --save-dev

Now that we have grunt installed, let’s install some task runners:

$ npm install grunt-contrib-copy --save-dev
$ npm install grunt-ts --save-dev
$ npm install grunt-contrib-watch --save-dev

The grunt-contrib-copy task runner will copy over files in the ./public and ./views directories into the ./dist directory.

We will use grunt-ts task to compile the TypeScript source.

And we will use grunt-contrib-watch to watch for any changes to our TypeScript source files. When a file is updated (or saved) then I want to re-compile my application. Combined with our dev script that we created in the package.json file earlier we will be able to easily make changes to the TypeScript source and then view the changes instantly in the browser.

Create gruntfile.js

The next step is to configure Grunt to compile our TypeScript source code. First, create the gruntfile.js file in the application root (heros in this example).

$ touch gruntfile.js

Open up the gruntfile.js file in your favorite editor. FWIW, I am using Visual Studio Code.

module.exports = function(grunt) {
  "use strict";

  grunt.initConfig({
    copy: {
      build: {
        files: [
          {
            expand: true,
            cwd: "./public",
            src: ["**"],
            dest: "./dist/public"
          },
          {
            expand: true,
            cwd: "./views",
            src: ["**"],
            dest: "./dist/views"
          }
        ]
      }
    },
    ts: {
      app: {
        files: [{
          src: ["src/\*\*/\*.ts", "!src/.baseDir.ts"],
          dest: "./dist"
        }],
        options: {
          module: "commonjs",
          target: "es6",
          sourceMap: false,
          rootDir: "src"
        }
      }
    },
    watch: {
      ts: {
        files: ["src/\*\*/\*.ts"],
        tasks: ["ts"]
      },
      views: {
        files: ["views/**/*.pug"],
        tasks: ["copy"]
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-copy");
  grunt.loadNpmTasks("grunt-contrib-watch");
  grunt.loadNpmTasks("grunt-ts");

  grunt.registerTask("default", [
    "copy",
    "ts"
  ]);

};

Here is a quick explanation of gruntfile.js:

  • Using the exports object we are exporting a function that will be invoked by the grunt task runner. This is pretty standard. It has a single argument named grunt.
  • Following best practices I am enabling strict mode.
  • Then we invoke the grunt.initConfig() method and pass in our configuration object.
  • Inside our configuration object we specify each task.
  • The first task is copy. This task will copy over the files in our ./public and ./views directories.
  • The next task is ts. This task will compile the TypeScript source code to JavaScript that can be executed by Node.js. The compiled JavaScript code will be output to ./dist directory.
  • The third task is watch. This task will watch for any changes to our TypeScript source files (*.ts) as well as our view template files (*.pug).

If everything works you should be able to execute the grunt command.

$ npm run grunt

And you should see something like this:

The grunt command should execute successfully

Install Middleware

Before we can create our server.ts module we need to install some more dependencies. I am using the following middleware in this example Express application:

You can read more about each of these using the links above. Let’s go ahead and get these installed via npm:

$ npm install body-parser --save
$ npm install cookie-parser --save
$ npm install morgan --save
$ npm install errorhandler --save
$ npm install method-override --save

We also need to install the TypeScript declaration files for each of these modules. Previous to TypeScript 2 you had to use an open-source project called Typings. This is no longer the case as TypeScript 2 has greatly improved support for third-party module declarations (or header files).

Let’s install the TypeScript declaration files using the @types/ repository on npmjs.org:

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

Create Server Class

To get started create a src directory for your TypeScript code and then create a new server.ts file.

$ mkdir src
$ cd src
$ touch server.ts

We are almost ready to fire up our new HTTP server using Express on Node.js. Before we do that we need to create our Server class. This is the class that will configure our express web application, the REST API, and the routes.

Here is the beginning of the server.ts file that defines our Server class:

import * as bodyParser from "body-parser";
import * as cookieParser from "cookie-parser";
import * as express from "express";
import * as logger from "morgan";
import * as path from "path";
import errorHandler = require("errorhandler");
import methodOverride = require("method-override");

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

  public app: express.Application;

  /**
   * Bootstrap the application.
   *
   * @class Server
   * @method bootstrap
   * @static
   * @return {ng.auto.IInjectorService} Returns the newly created injector for this app.
   */
  public static bootstrap(): Server {
    return new Server();
  }

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

    //configure application
    this.config();

    //add routes
    this.routes();

    //add api
    this.api();
  }

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

  /**
   * Configure application
   *
   * @class Server
   * @method config
   */
  public config() {
    //empty for now
  }

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

Let’s dig into the Server class, which is inside of the server.ts module (file).

Importing

import * as bodyParser from "body-parser";
import * as cookieParser from "cookie-parser";
import * as express from "express";
import * as logger from "morgan";
import * as path from "path";
import errorHandler = require("errorhandler");
import methodOverride = require("method-override");
  • First we import the middleware and necessary modules that we previously installed.
  • The body-parser middleware will parse JSON payload data into the req.body object that will be available in our express application.
  • The cookie-parser middleware is similar to the body-parser in that it parses the user’s cookie data and makes this available in the req.cookies object.
  • We then import the express module. This is the express framework.
  • I am using the morgan HTTP logger middleware. This should only be used during development.
  • I then import the path module. I will use this to set the path directories for public and views directories in the config() method.
  • The errorhandler middleware will handle errors during development. Again, this should not be used in production. Rather, you will want to log your errors and then show the user a error indication.
  • Finally, I am using the method-override middleware. You may not need this, but this is required when using “PUT” and “DELETE” HTTP verbs in your REST API configuration.

The Server class

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

  public app: express.Application;

  /**
   * Bootstrap the application.
   *
   * @class Server
   * @method bootstrap
   * @static
   * @return {ng.auto.IInjectorService} Returns the newly created injector for this app.
   */
  public static bootstrap(): Server {
    return new Server();
  }
}

Next we create a new class called Server. Our class has a single publicly available variable named app. Note that our app is of type express.Application.

Inside the Server class I have a static method named bootstrap(). This is invoked in our www startup script. It simply creates a new instance of the Server class and returns it.

The constructor Function

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

  //configure application
  this.config();

  //add routes
  this.routes();

  //add api
  this.api();
}

In the constructor() function I set the value of the app property by creating a new express application. I am then calling some methods that are defined in the Server class to configure my application and to create my app’s REST API and HTTP routes. For now these are left empty.

You might want to test things out at this point. While we don’t have our HTTP server configured yet, we should be able to compile our TypeScript using grunt:

$ npm run grunt

You should see an indication that the compilation completed successfully:

The grunt command should execute successfully

Configure Server

The next step is to implement the config() method in our Server class:

/**
  * Configure application
  *
  * @class Server
  * @method config
  */
public config() {
  //add static paths
  this.app.use(express.static(path.join(__dirname, "public")));

  //configure pug
  this.app.set("views", path.join(__dirname, "views"));
  this.app.set("view engine", "pug");

  //use logger middlware
  this.app.use(logger("dev"));

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

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

  //use cookie parser middleware
  this.app.use(cookieParser("SECRET_GOES_HERE"));

  //use override middlware
  this.app.use(methodOverride());

  //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 notes on the config() method:

  • First, I set up a static path at /public. Any files located in the ./public folder will be publicly accessible (duh).
  • Next, I configure the pug template engine. We’ll install this in a minute. All of our pug template files will be located in the ./views directory.
  • Then we add the morgan logger middleware.
  • Then we add the body-parser middleware to parse JSON as well as the query string.
  • Then we add the cookie-parser middleware.
  • Then we add the method-override middleware.
  • Finally, we add some code to catch 404 errors as well as any application exceptions.

As I indicated above we are using the pug template engine. Before we can use this, however, we need to install it via npm:

$ npm install pug --save

We should also create the public and views directories:

$ mkdir public
$ mkdir views

Here is what our directory structure should look like:

.
├── bin
│   └── www
├── dist
│   └── server.js
├── gruntfile.js
├── package.json
├── public
├── src
│   ├── npm-debug.log
│   └── server.ts
└── views

Now that our server is configured we should be able to compile our TypeScript source code and start the node HTTP server:

$ npm run grunt
$ npm start

You should then see that node is running:

Our node server is running

Go ahead and load up your brand new Express + Node.js server at http://localhost:8080 and you should see:

Our node server is running

This is because we have not defined any routes yet. Let’s go ahead and do that next.

Create BaseRoute Class

Now that our server is configured and running we are ready to start building our web application’s routes. But, you might be asking yourself what a route is. Well, according to the Express documentation:

Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).

First, let’s create a ./src/routes directory with two new files: route.ts and index.ts.

$ cd ./src
$ mkdir routes
$ touch route.ts
$ touch index.ts

The route.ts module will export the BaseRoute class. All of the route classes will extend the BaseRoute. Let’s take a look.

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

/**
 * Constructor
 *
 * @class BaseRoute
 */
export class BaseRoute {

  protected title: string;

  private scripts: string[];

  /**
   * Constructor
   *
   * @class BaseRoute
   * @constructor
   */
  constructor() {
    //initialize variables
    this.title = "Tour of Heros";
    this.scripts = [];
  }

  /**
   * Add a JS external file to the request.
   *
   * @class BaseRoute
   * @method addScript
   * @param src {string} The src to the external JS file.
   * @return {BaseRoute} Self for chaining
   */
  public addScript(src: string): BaseRoute {
    this.scripts.push(src);
    return this;
  }

  /**
   * Render a page.
   *
   * @class BaseRoute
   * @method render
   * @param req {Request} The request object.
   * @param res {Response} The response object.
   * @param view {String} The view to render.
   * @param options {Object} Additional options to append to the view's local scope.
   * @return void
   */
  public render(req: Request, res: Response, view: string, options?: Object) {
    //add constants
    res.locals.BASE_URL = "/";

    //add scripts
    res.locals.scripts = this.scripts;

    //add title
    res.locals.title = this.title;

    //render view
    res.render(view, options);
  }
}

My BaseRoute is currently pretty thin. But, this will serve as a way to implement authentication in my application later, and possibly many other features that all routes need to have.

I have a title string variable that will hold the title for the route.

As an example the BaseRoute currently stores an array of scripts that are necessary for a specific route. You might also want to define scripts in your BaseRoute that all routes will require. This is just an example of a feature that you may want to implement in the BaseRoute that will be available to all of your routes.

Further, the BaseRoute class has a render() method. This will be invoked in each of the route methods in our extending classes. This provides us a single method to render a view with common local template variables defined.

In this example, I am setting BASE_URL, scripts and title into each view.

Create IndexRoute Class

A route definition is defined via:

app.METHOD(PATH, HANDLER)

Where METHOD is the appropriate HTTP verb, such as get or post. The method should be in lowercase. The PATH is the requested URI path. And, the HANDLER is a function that is executed when the route is matched.

The index.ts module will export the IndexRoute class. Let’s take a look at that.

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


/**
 * / route
 *
 * @class User
 */
export class IndexRoute extends BaseRoute {

  /**
   * Create the routes.
   *
   * @class IndexRoute
   * @method create
   * @static
   */
  public static create(router: Router) {
    //log
    console.log("[IndexRoute::create] Creating index route.");

    //add home page route
    router.get("/", (req: Request, res: Response, next: NextFunction) => {
      new IndexRoute().index(req, res, next);
    });
  }

  /**
   * Constructor
   *
   * @class IndexRoute
   * @constructor
   */
  constructor() {
    super();
  }

  /**
   * The home page route.
   *
   * @class IndexRoute
   * @method index
   * @param req {Request} The express Request object.
   * @param res {Response} The express Response object.
   * @next {NextFunction} Execute the next method.
   */
  public index(req: Request, res: Response, next: NextFunction) {
    //set custom title
    this.title = "Home | Tour of Heros";

    //set options
    let options: Object = {
      "message": "Welcome to the Tour of Heros"
    };

    //render template
    this.render(req, res, "index", options);
  }
}

Let’s review the IndexRoute class:

  • First, we import the NextFunction, Request, Response and Router classes from the express module.
  • I also import the BaseRoute class from the routes module.
  • The create() static method creates all of the routes that will be defined in the class. In this example, I am only defining a single route. But, you will likely have several routes defined for section of your application. For example, a UsersRoute class might have routes for /users/signin, /users/signup, etc.
  • The constructor() function simply invokes the BaseRoute’s constructor function.
  • The index() function will render our template. Before we render the template we set a custom title as well as define an object named options which contains properties and values that will be available in our template. In this example I am setting a local template variable named message that contains a simple string. I’ll output this in the index.pug template.

Define routes

Now that we have created the shell of our first route we need to define this in our Server class. However, before we define the route, let’s first import our IndexRoute class in our server.ts module via:

import { IndexRoute } from "./routes/index";

Then, let’s implement the routes() method in the server.ts module:

/**
  * Create router.
  *
  * @class Server
  * @method config
  * @return void
  */
private routes() {
  let router: express.Router;
  router = express.Router();

  //IndexRoute
  IndexRoute.create(router);

  //use router middleware
  this.app.use(router);
}

In the routes() method we create the express.Router() instance. We then invoke the static IndexRoute.create() method and pass in the router instance. Finally, we add the router middleware to our application.

Create Template

Now that we have the routes created and defined we need to create the necessary template. In this example I am going to create a single index.pug file in the views directory.

$ cd ./views
$ touch index.pug

Here is what my example index.pug file looks like:

html
  head
    title= title
  body
    h1= message

Serve it up!

We made it. We have built a solid foundation for developing an application using Express from TypeScript source code. The next step is to compile and start the server.

$ npm run grunt
$ npm start

Now fire up your browser and go to http://localhost:8080. If everything is working you should see the following message in your browser:

Welcome to the Tour of Heros

Install nodemon

If you want to start our node server that will watch for any changes to the source code (while in development) then I suggest you use nodemon.

$ npm install nodemon --save-dev

We can then run our custom dev script that we defined in package.json to start our application use nodemon:

$ npm run dev

Source Code

Add MongoDB + Mocha + Chai

If you loved this post then check out TypeScript 2 + Express + Mongoose + Mocha + Chai.

Now, I know that sounds like a lot.

I’ll walk you through getting everything installed and configured so that your Express web application can persist data to a MongoDB collection. Then we’ll use the Mocha testing framework with Chai assertions to make sure it works.

Brian Love

Hi, I'm Brian. I am interested in TypeScript, Angular and Node.js. I'm married to my best friend Bonnie, I live in Denver and I ski (a lot).