Tuesday, November 22, 2016

Creating a Todo web app using Angular2 and Aurelia for Comparison

 

Introduction

YouTube Video Link:

In this article, I’ll show how to create a TodoMVC application using Angular2 and Aurelia to compare these frameworks. The article is in a table format with 2 columns named “Angular2” and Aurelia, which contains the steps to create a TodoMVC application in both these frameworks. The projects are created using CLI (Command Line Interface) tools that are part of these frameworks. The source code for these projects are available in my github page:

Angular2 Source Code: https://github.com/rajajhansi/ng2-todo-app

Aurelia Source Code: https://github.com/rajajhansi/au-todo-app

 

Steps to create a Todo MVC Application in Angular2 and Aurelia

The table below lists the steps to create a Todo MVC Application in Angular2 and Aurelia.

Angular2

Aurelia

Installing the ng CLI
npm install angular-cli -g
Installing the au CLI
npm install aurelia-cli -g
Creating a new application
ng new ng2-todo-app
Creating a new application
au new au-todo-app
Running the application
cd ng2-todo-app
ng serve
Running the application
cd au-todo-app
au run --watch
Creating a Todo class
ng g class Todo
Creating a Todo class
You can either run the command:
au generate Todo
and move src/resources/Todo.ts into src folder and delete src/resources/elements/Todo.html
      OR
Create a new file named Todo.ts in src folder, which is easier.
todo.ts
export class Todo {  
	id: number;  
	description: string = '';  
	done: boolean = false;
  	constructor(values: Object = {}) {   
 		Object.assign(this, values);  
	}
}
todo.ts
export class Todo {  
	id: number;  
	description: string = '';  
	done: boolean = false;
  	constructor(values: Object = {}) {   
 		Object.assign(this, values);  
	}
}

Creating unit test for Todo class using jasmine - Angular2
todo.spec.ts

/* tslint:disable:no-unused-variable */

import { TestBed, async } from '@angular/core/testing';
import {Todo} from './todo';

describe('Todo', () => {

  it('should create an instance', () => {
    expect(new Todo()).toBeTruthy();
  });

  it('should accept values in the constructor', () => {
    let todo = new Todo({
      description: 'hello',
      done: true
    });
    expect(todo.description).toEqual('hello');
    expect(todo.done).toEqual(true);
  });

  it('should ignore values sent in wrong properties in the constructor', () => {
    let todo = new Todo({
      title: 'hello',
      done: true
    });
    expect(todo.description).toEqual('');
    expect(todo.done).toEqual(true);
  });

});

Creating unit test for Todo class using jasmine - Aurelia
todo.spec.ts

/* tslint:disable:no-unused-variable */
import {Todo} from '../../src/todo';

describe('Todo', () => {

  it('should create an instance', () => {
    expect(new Todo()).toBeTruthy();
  });

  it('should accept values in the constructor', () => {
    let todo = new Todo({
      description: 'hello',
      done: true
    });
    expect(todo.description).toEqual('hello');
    expect(todo.done).toEqual(true);
  });

  it('should ignore values sent in wrong properties in the constructor', () => {
    let todo = new Todo({
      title: 'hello',
      done: true
    });
    expect(todo.description).toEqual('');
    expect(todo.done).toEqual(true);
  });

});


Todo Service Class - Angular2
todo.service.ts
import { Injectable } from '@angular/core';
import { Todo } from './todo';

@Injectable()
export class TodoService {

  lastId: number = 0;

  todos: Todo[] = [];

  constructor() { }

  addTodo(todo: Todo): TodoService {
    if(!todo.id) {
      todo.id = ++this.lastId;
    }
    this.todos.push(todo);
    return this;
  }

  deleteTodo(id: number): TodoService {
    this.todos = this.todos.filter(todo => todo.id !== id);
    return this;
  }

  updateTodo(id: number, values: Object = {}): Todo {
    let todo = this.getTodo(id);
    if (!todo) {
      return null;
    }
    Object.assign(todo, values);
    return todo;
  }

  getAllTodos(): Todo[] {
    return this.todos;
  }

  getTodo(id: number): Todo {
    return this.todos.filter(todo => todo.id === id).pop();
  }

  toggleTodoDone(todo: Todo): Todo {
    let updatedTodo = this.updateTodo(todo.id, { done: !todo.done });
    return updatedTodo;
  }

  filterTodo(filterCriteria: string): Todo[] {
    switch (filterCriteria) {
      case 'active':
        return this.todos.filter((t: Todo) => !t.done);
      case 'completed':
        return this.todos.filter((t: Todo) => t.done);
      case 'all':
      default:
        return this.todos;
    }
  }
  completeAllTodos() {
    this.todos.forEach((t: Todo) => t.done = true);
  }

  removeAllTodos() {
    this.todos.splice(0);
  }

  removeDoneTodos() {
    this.todos = this.todos.filter((todo: Todo) => !todo.done);
  }
}

Todo Service Class - Aurelia
todo.service.ts
import { Todo } from './todo';

export class TodoService {

  lastId: number = 0;

  todos: Todo[] = [];

  constructor() { }

  addTodo(todo: Todo) : TodoService {
    if(!todo.id) {
      todo.id = ++this.lastId;
    }
    this.todos.push(todo);
    return this;
  }

  deleteTodo(id: number) : TodoService {
    this.todos = this.todos.filter(todo => todo.id !== id);
    return this;
  }

  updateTodo(id: number, values: Object = {}): Todo {
    let todo = this.getTodo(id);
    if(!todo) {
      return null;
    }
    Object.assign(todo, values);
    return todo;
  }

  getAllTodos(): Todo[] {
    return this.todos;
  }

  getTodo(id: number): Todo {
    return this.todos.filter(todo => todo.id === id).pop();
  }

  toggleTodoDone(todo: Todo): Todo {
    let updatedTodo = this.updateTodo(todo.id, { done: !todo.done });
    return updatedTodo;
  }

  filterTodo(filterCriteria: string) : Todo[] {
    switch(filterCriteria) {
      case "active":
        return this.todos.filter((t: Todo) => !t.done);
      case "completed":
        return this.todos.filter((t: Todo) => t.done);
      case "all":
      default:
        return this.todos;
    }     
  }

  completeAllTodos() {
    this.todos.forEach((t: Todo) => t.done = true);
  }

  removeAllTodos() {
    this.todos.splice(0);
  }
  
  removeDoneTodos() {
    this.todos = this.todos.filter((todo: Todo) => !todo.done);
  }
}

Running unit tests
ng test
Running unit tests
au build
au test
Creating a Service class for Todo
ng g service Todo
Creating a Service class for Todo
create a new folder named “src\services” and a file named todo.service.ts
Installing the TodoMVC Stylesheets
npm install todomvc-app-css todomvc-common --save

Adding custom style for toolbar

Installing the TodoMVC Stylesheets
npm install todomvc-app-css todomvc-common --save

Adding custom style for toolbar

styles.css - Angular2
/* You can add global styles to this file, and also import other style files */

@import url('../node_modules/todomvc-common/base.css');
@import url('../node_modules/todomvc-app-css/index.css');

.app-root-loader {
	text-align: center;
	padding: 30px;
}

.toolbar {
	color: #777;
	/*background-color: lightslategray;*/
	padding: 5px 10px;
	height: 20px;
	text-align: center;
	border-top: 
	1px solid #e6e6e6;
	float: right;
}


styles.css - Aurelia
/* You can add global styles to this file, and also import other style files */

@import url('../node_modules/todomvc-common/base.css');
@import url('../node_modules/todomvc-app-css/index.css');

.app-root-loader {
	text-align: center;
	padding: 30px;
}

.toolbar {
	color: #777;
	/*background-color: lightslategray;*/
	padding: 5px 10px;
	height: 20px;
	text-align: center;
	border-top: 
	1px solid #e6e6e6;
	float: right;
}

Adding a new Todo Item in text box after entering the description and pressing enter key
Angular2 has (keyup.enter) event to trigger a JavaScript function upon pressing enter key after entering the description for a new Todo item.
Adding a new Todo Item in text box after entering the description and pressing enter key

Aurelia recommends to use <form> and wire the submit.trigger to trigger a JavaScript function upon pressing enter key. It also requires a <button type=”submit”> as well. To attach enter keypress event to any element, we can easily do that with Aurelia Custom attribute.


au generate attribute keyup-enter

keyup-enter.ts - Aurelia
import {autoinject} from 'aurelia-framework';

@autoinject()
export class KeyupEnterCustomAttribute {
    element: Element;
    value: Function;
    enterPressed: (e: KeyboardEvent) => void;

    constructor(element: Element) {
        this.element = element;

        this.enterPressed = e => {
            let key = e.which || e.keyCode;
            if (key === 13) {
                this.value();//'this' won't be changed so you have access to your VM properties in 'called' method
            }
        };
    }

    attached() {
        this.element.addEventListener('keypress', this.enterPressed);
    }

    detached() {
        this.element.removeEventListener('keypress', this.enterPressed);
    }
}

Creating the application component
ng g component TodoApp
Creating the application component
au generate element todo-app

Aurelia has the concept of global resources and by adding a resource into global resources, you can use them in any views without having to do <require from=”path of the resource”></require>
src/resources/index.ts

import {FrameworkConfiguration} from 'aurelia-framework';

export function configure(config: FrameworkConfiguration) {
  config.globalResources([
    "./elements/todo-app",
    "./attributes/keyup-enter"
  ]);
}

Wiring Todo Item functionalities into the App component - Angular2
todo-app.component.ts
import { Component, OnInit } from '@angular/core';
import { Todo } from '../todo';
import { TodoService } from '../todo.service';

@Component({
  selector: 'todo-app',
  templateUrl: './todo-app.component.html',
  styleUrls: ['./todo-app.component.css'],
  providers: [TodoService]
})
export class TodoAppComponent implements OnInit {
  newTodo: Todo = new Todo();
  filter: string = 'all';
  filteredTodos: Todo[] = [];

  constructor(private todoService: TodoService) { }

  ngOnInit() {
  }

  addTodo() {
    this.todoService.addTodo(this.newTodo);
    this.newTodo = new Todo();
    this.filterTodo(this.filter);
  }

  toggleTodoDone(todo) {
    this.todoService.toggleTodoDone(todo);
    this.filterTodo(this.filter);
  }

  removeTodo(todo) {
    this.todoService.deleteTodo(todo.id);
    this.filterTodo(this.filter);
  }

  filterTodo(filterCriteria: string) {
    this.filter = filterCriteria;
    this.filteredTodos = this.todoService.filterTodo(filterCriteria);
  }

  completeAllTodos() {
    this.todoService.completeAllTodos();
    // this.checkIfAllTodosAreCompleted();
    this.filterTodo(this.filter);
  }

  removeAllTodos() {
    this.todoService.removeAllTodos();
    this.filterTodo(this.filter);
  }

  removeDoneTodos() {
    this.todoService.removeDoneTodos();
    this.filterTodo(this.filter);
  }
}

Wiring Todo Item functionalities into the App component - Aurelia
todo-app.ts
import {inject} from 'aurelia-framework';
import {Todo} from '../../todo';
import {TodoService} from '../../todo.service';

@inject(TodoService)
export class TodoApp {
  newTodo: Todo = new Todo();
  filter: string = "all";
  filteredTodos: Todo[] = [];
  constructor(private todoService: TodoService) { }

  addTodo() {
    this.todoService.addTodo(this.newTodo);
    this.newTodo = new Todo();
    this.filterTodo(this.filter);
  }

  toggleTodoDone(todo) {
    this.todoService.toggleTodoDone(todo);
    this.filterTodo(this.filter);
  }

  removeTodo(todo) {
    this.todoService.deleteTodo(todo.id);
    this.filterTodo(this.filter);
  }

  filterTodo(filterCriteria: string) {
    this.filter = filterCriteria;
    this.filteredTodos = this.todoService.filterTodo(filterCriteria);
  }

  completeAllTodos() {
    this.todoService.completeAllTodos();
    //this.checkIfAllTodosAreCompleted();
    this.filterTodo(this.filter);
  }

  removeAllTodos() {
    this.todoService.removeAllTodos();
    this.filterTodo(this.filter);
  }

  removeDoneTodos() {
    this.todoService.removeDoneTodos();
    this.filterTodo(this.filter);
  }
}

Wiring Todo Item functionalities into the App View - Angular2
todo-app.component.html
<section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <div class="toolbar">
    <a href="#" (click)="removeAllTodos()">
        Remove All
      </a> |
      <a href="#"  (click)="removeDoneTodos()">
        
        Remove Completed
      </a> |
      <a href="#"  (click)="completeAllTodos()">
        Complete All 
      </a>
    </div>
    <br/>
    <input type="text" class="new-todo" placeholder="what needs to be done?" autofocus="" 
      [(ngModel)]="newTodo.description" (keyup.enter)="addTodo()">
  </header>

  <section class="main" *ngIf="filteredTodos.length > 0">
    <ul class="todo-list">
      <li *ngFor="let todo of filteredTodos" [class.completed]="todo.done">
        <div class="view">
          <input type="checkbox" class="toggle" (click)="toggleTodoDone(todo)" [checked]="todo.done">
          <label>{{todo.description}}</label>
          <button class="destroy" (click)="removeTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>

  <footer class="footer" >
    <span class="todo-count"><strong>{{filteredTodos.length}}</strong> {{filteredTodos.length === 1 ? 'item': 'items'}} left</span>
    <ul class="filters">
      <li>
        <a class="{{filter == 'all' ? 'selected' : ''}}" href="#" (click)="filterTodo('all')">All</a>
      </li>
      <li>
        <a class="{{filter == 'active' ? 'selected' : ''}}" href="#" (click)="filterTodo('active')">Active</a>
      </li>
      <li>
        <a class="{{filter == 'completed' ? 'selected' : ''}}" href="#" (click)="filterTodo('completed')">Completed</a>
      </li>
    </ul>
  </footer>
</section>

Wiring Todo Item functionalities into the App View - Aurelia
todo-app.html
<template>  
  <section class="todoapp">
  <header class="header">
    <h1>Todos</h1>
    <div class="toolbar">
      <a href="#" click.trigger="removeAllTodos()">
        Remove All
      </a> |
      <a href="#"  click.trigger="removeDoneTodos()">
        
        Remove Completed
      </a> |
      <a href="#"  click.trigger="completeAllTodos()">
        Complete All 
      </a>
    </div>
    <br/>
    <input type="text" class="new-todo" placeholder="what needs to be done?" autofocus="" 
      value.bind="newTodo.description" keyup-enter.call="addTodo()">
  </header>

  <section class="main" show.bind="filteredTodos.length > 0">
    <ul class="todo-list">
      <li repeat.for="todo of filteredTodos" class="${todo.done ? 'completed' : ''}">
        <div class="view">
          <input type="checkbox" class="toggle" checked.bind="todo.done">
          <label>${todo.description}</label>
          <button class="destroy" click.trigger="removeTodo(todo)"></button>
        </div>
      </li>
    </ul>
  </section>

  <footer class="footer">
    <span class="todo-count"><strong>${filteredTodos.length}</strong>${filteredTodos.length === 1 ? ' item ': ' items '} left</span>
    <ul class="filters">
      <li>
        <a class="${filter == 'all' ? 'selected' : ''}" href="" click.trigger="filterTodo('all')">All</a>
      </li>
      <li>
        <a class="${filter == 'active' ? 'selected' : ''}" href="" click.trigger="filterTodo('active')">Active</a>
      </li>
      <li>
        <a class="${filter == 'completed' ? 'selected' : ''}" href="" click.trigger="filterTodo('completed')">Completed</a>
      </li>
    </ul>
  </footer>
</section>

</template>

Using custom element in app.html - Angular2
app.html

<todo-app></todo-app>

Using custom element in app.html - Aurelia
app.html

<template>
<require from="./styles.css"></require>
  <todo-app></todo-app>
</template>

 

Comparison of Angular2 and Aurelia

 

 

Angular2

Aurelia

I see the framework code explicitly in my application code because Angular2 uses explicit declarations via decorators
I don’t see the framework polluting my application code because Aurelia uses conventions
Hard to switch to another framework because my application has too much of framework code.
Easy to switch to another framework because my application code is reusable JavaScript code
Uses non-standard syntax like [(ngModel)], *ngFor, *ngIf, (event), {{ }} for interpolation etc., in the HTML markup that are not part of the standards. Follows web standards like web components, ECMAScript 6 string interpolation etc.
Angular2 has great support from 3rd party vendors like Telerik who are writing the Angular2 directives. Aurelia has great support from Syncfusion(http://aureliajq.syncfusion.com/). Aurelia UX bridge project is a community run project that has created components for Telerik KendoUI and Materialize.
Angular2 is more popular because it is from google and people hear about it everywhere be it a conference or other software companies’ web sites like Microsoft ASP.NET Core or MSDN or Channel 9 etc. Aurelia is gaining momentum because its creators are presenting in popular conferences like NDC and sites like Channel 9 etc. Aurelia is also supported for .NET Core officially and talked about in MSDN magazine and on Channel 9. More Channel 9 content is coming soon.
Angular2 has been in development for a very long time. Angular2 documentation has gotten much better compared to Angular 1 documentation. Aurelia has a very aggressive release cycle and they have frequent releases. The Aurelia.io hub documentation has gotten much better, very detailed oriented, technical and useful for anyone who wants to learn the framework.
Plugins are available for IntelliJ, VSCode and other popular editors out there. Plunkr supports Angular2 templates for quick online application development. Plugins are available for IntelliJ, VSCode and other popular editors out there. Aurelia team built GistRun (https://gist.run/) using Aurelia itself that can run your gists and is very handy for online application development. Aurelia has the plunker resources (https://github.com/jdanyow/aurelia-plunker) created by core team member Jeremey Dayow for use as well.
There are a lot of books out there (started using the early releases of Angular2 but are constantly updated) already for Angular2. There are only 2 books available out there for Aurelia (both are still under development) now. There are more books and video trainings coming soon.
There are many learning resources like video tutorials out there from Pluralsight, Wintellect, egghead etc. besides a lot of other tutorials on YouTube. There are so many enthusiasts who blog regularly on Angular. There are so many enthusiasts who regularly blog about Aurelia besides Aurelia’s core team members. There aren’t that many learning resources out there but the ones that are available are really top quality resources. Rob Eisenberg’s official 3 part Aurelia Virtual Training videos (https://www.picatic.com/modern-javascript-and-aurelia) on Vimeo are awesome.
It is a great move from the Angular2 team to use TypeScript, which is a typed superset of JavaScript instead of their proprietary Dart language. Aurelia itself is written using ES6 & 7 and has great starter kits (aka their skeleton navigation projects) in both ES6 and TypeScript. Some of the new plugins are built using TypeScript.
When it comes to  hiring front end developers to work in Angular2 projects, you need to look for skills like TypeScript, understanding of module systems like Webpack or SystemJs, and give an in-depth training on Angular2 fundamentals because it has a steep learning curve. When it comes to hiring front end developers to work in Aurelia projects, all you need to look for is skills like JavaScript (ES6 or TypeScript is a plus), understanding of module systems like Webpack or SystemJs or RequireJs and give some training on Aurelia fundamentals because it has a simple learning curve.
Although you’ll find many front end developers who might have heard about Angular2, knowledge of Angular 1 is not required at all because the framework has changed so much from version 1. You’ll not find that many front end developers who might have heard about Aurelia, which shouldn’t matter because the framework itself is about 1.5 years old. Since it is a conventions based framework, it should be very easy to train any front end developer on Aurelia.

 

Conclusion

Both Angular2 and Aurelia are very modern JavaScript frameworks with  very good open source community backing them up.

If I were to start a project today, I would pick a modern, modular and standards based framework like Aurelia. While others may have a different opinion which is perfectly fine, my suggestion to the readers of this blog post would be to pick a framework that works for you, your team and your company.

Happy Coding!

Sunday, October 23, 2016

Building a web application using ASP.NET Core 1.0, Node.Js, Express/Hapi/Restify, TypeScript and Visual Studio Code - Part 6, Sec 3

Introduction

YouTube Video Link for Part 6, Section 1:

YouTube Video Link for Part 6, Section 2:

YouTube Video Link for Part 6, Section 3:

This is the third section of the sixth part of Building a web application using ASP.NET Core 1.0, Node.Js, Aurelia, TypeScript, Webpack in Visual Studio Code.

The source code for this tutorial is available on GitHub.

In this section of part 6, I’ll show how to enable CORS (Cross Origin Resource Sharing) on the ExpressApplication and  HapiApplication. Then, I’ll show how to include both Asp.Net Core and NodeJs backend REST API implementations to work with a single copy of front end Aurelia client code in a single folder structure.  Finally, I’ll show you how you can use either Asp.Net Core or NodeJs as your REST API endpoints by changing a configuration value in the front end code.

 

Enabling CORS in ExpressApplication and HapiApplication

Since our client application’s port and our server side REST API’s port will be different, we need to enable CORS to support cross-origin requests. We have already done this in our RestifyApplication class but we need to enable CORS in both ExpressApplication and HapiApplication.

To allow CORS, we need to add the headers “Access-Control-Allow-Origin”, “Access-Control-Allow-Headers” and “Access-Control-Allow-Methods”  to the allows origins like “http://localhost:9000”, allowed headers like “Origin, Accept, Authorization” and allowed HTTP methods/verbs like “GET, POST, PUT, DELETE” etc. For demonstration purposes, we can use “*” as the value to allow any origin, any header or any method, which is not recommended for production deployment. When I talk about production deployment in later part of the series, I’ll show how to restrict them to only the absolutely required origins, headers and HTTP Verbs.

Instead of manually setting these headers and their values, let us leverage the cors module for expressjs. To install the cors module for expressjs and its typescript typing definition, run the commands:

npm install cors --save
typings install dt~cors --save --global

We need to import the cors module like this:

import * as cors from "cors";

Let us refactor the constructor of ExpressApplication and add CORS support into the bootstrap method like this:

express-application.ts
 
    this.expressApplication.use(cors(), (request: express.Request, response: express.Response, next: express.NextFunction) => {
        console.log("CORS enabled for all routes");
        next();
    });

    // configure API and error routes   
    this.expressContactRouter.configApiRoutes(this.expressApplication);
    this.expressContactRouter.configErrorRoutes(this.expressApplication);
    this.expressApplication.set("port", port);
    ...
}

 

Let us refactor the constructor of HapiApplication and add CORS support into the bootstrap method like this:

hapi-application.ts
public bootstrap(port: number) {
    this.hapiApplication.connection({port: port, routes: { cors: true }});
    ...

Let us refactor the constructor of RestifyApplication and move CORS support into the bootstrap method like this:

restify-application.ts
public bootstrap(port: number) {
    this.restifyApplication.use(restify.CORS());
    this.restifyApplication.use(restify.pre.sanitizePath());
    this.restifyApplication.use(restify.acceptParser(this.restifyApplication.acceptable));
    this.restifyApplication.use(restify.bodyParser());
    this.restifyApplication.use(restify.queryParser());
    this.restifyApplication.use(restify.authorizationParser());
    this.restifyApplication.use(restify.fullResponse());

    // configure API and error routes
    this.restifyContactRouter.configApiRoutes(this.restifyApplication);
    this.restifyContactRouter.configErrorRoutes(this.restifyApplication);
    ...
}

Notice that I removed the line restify.CORS.ALLOW_HEADERS.push("authorization"); because we don’t need to add this header explicitly. We’ll revisit this header when I talk about authentication and authorization in a later part of this series.

 

Generating UML class diagrams from TypeScript modules

 

You can generate UML diagram from TypeScript modules using a simple tool named tsviz. The github repository for this tool is https://github.com/joaompneves/tsviz. To install the tool use the command:

npm install tsviz -g

To run the tool and create a diagram for an entire project, you use the command:

tsviz server/src/contact-manager/ -recursive contact-manager-class-diagram.png 

Modify the route for GET method to take an optional id parameter

Since the Aurelia client application will need to pass an optional id parameter to the GET endpoint, let us add that optional parameter to all three router classes.

Let us modify the configApiRoutes() method in ExpressContactRouter like this:

express-contact-router.ts
"use strict";
configApiRoutes(expressApplication: express.Application) {
        this.contactRouter.route("/contacts/:id?")
        .get((request: express.Request, response: express.Response) => {
            if(request.params.id) {
                this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                    response.json(contact);
                });
            } else {
                this.contactService.getAll().then((contacts : IContact[]) => {
                    response.json(contacts);
                });
            }
        })
	...
}

 

Let us modify the configApiRoutes() method in HapiContactRouter like this:

  public configApiRoutes(hapiApplication: Hapi.Server) {
      var endpoint = "/api/contacts/{id?}";
        hapiApplication.route({
            path: endpoint,
            method: "GET",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
                if(request.params.id) {
                    this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                        reply(contact);
                    });
                } else {
                    this.contactService.getAll().then((contacts : IContact[]) => {
                        reply(contacts);
                    });
                }
            }});
	...
}

We need to add an additional endpoint that takes id parameter inside the configApiRoutes() method in RestifyContactRouter like this:

public configApiRoutes(restifyApplication: restify.Server) {
      let endpoint = "/api/contacts";
        restifyApplication.get(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
        {
            this.contactService.getAll().then((contacts : IContact[]) => {
                response.json(contacts);
                return next();
            });
        }));

        ...

        let idEndpoint = `${endpoint}/:id`;
        restifyApplication.get(idEndpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
        if(request.params.id) {
            this.contactService.get(parseInt(request.params.id, 10)).then((contact : IContact) => {
                response.json(contact);
                return next();
            });
        }

 

Merging source code from part5 into part6 to have both Asp.Net Core and NodeJs backend APIs with a single Aurelia front end source code

We are interested to keep our ASP.NET Core backend and the Node.js backend server code in the same project structure with the Aurelia client code shared between them. We need to first bring the source code from part5 into part6 using the following steps:

1. Create a client folder under part6\modern folder

2. Copy part5/modern/src folder into part6/modern/client folder

3. Copy all files and folders in part5/modern except the src folder into part6/modern folder

merge-part5-and-part6-source

4. We need to use the symbolic link to share the client code between Asp.Net core and NodeJs projects. Check out http://www.howtogeek.com/howto/windows-vista/using-symlinks-in-windows-vista/. We need to run the following command from within the part6/modern/nodejs folder:

cmd /c 'mklink /J client "../client"'

5. Make sure to exclude “client” folder in the “exclude” setting in bunder nodejs folder like this:

"exclude" : [
        "client",
        "node_modules"
    ]

6. Make sure to change the srcDir variable in webpack.config.js to point to ‘client/src’ like this:

const srcDir = path.resolve('client/src');

7. Change the webpack version in package.json

"webpack": "2.1.0-beta.22"

8. Run the following commands from part6\modern folder

npm install
typings install

9. Make sure to install TypeScript locally using the command:

npm install typescript@2.0 --save-dev

10. Make sure to exclude “nodejs” folder in tsconfig.json like this:

"exclude": [ 
	"nodejs", 
	...
]

 

11. Change the relative path in the import statements from line 3 to 8 in client/src/main.ts like this:

import "../../styles/styles.css";
import "font-awesome/css/font-awesome.css";
import "bootstrap/dist/css/bootstrap.css";
import "../../styles/bootstrap-flat.css";
import "../../styles/bootstrap-flat-extras.css";
import "../../styles/awesome-bootstrap-checkbox.css";

12. Run the client application using the command:

npm start

 

 

Running both Asp.Net Core 1.0 and NodeJs REST Service from 2 instances of VS Code

If we want to run multiple task runners from within a single instance of VS Code, we could do so using tasks.json and assigning custom key combinations to launch TypeScript compiler, DotNet compiler, npm, gulp etc. VS Code has launch.json to launch one type of application with “request” types like “launch” and “attach”. At the time of this writing, VS Code doesn’t allow us to define mulitple launch configurations say one for .NET Core and another for NodeJs. You can have one or another with both “request” types like “launch” and “attach” but not both project types with both “request” types.

 

The best way that I know of to run both Asp.Net Core and NodeJs applications within a single folder structure is to put them in different sub folders and launch 2 instances of VS Code.VS Code treats the folder from where you launch VS Code as the project folder. Technically, any folder where you have a .vscode folder is a project because .vscode folder is where you define tasks.json, launch.json and settings.json. Since we can’t and shouldn’t duplicate the client source code, we have already created a symlink inside of NodeJs folder pointing to the client source code within Asp.Net project folder. So, we don’t have to worry about maintaining 2 copies of the client code because synchronizing them would become a nightmare.

We should open up an instance of VS Code from part6/modern to debug the Asp.Net Core REST API as well as launching the Aurelia client web application. We should open up an instance of VS Code from part6/modern/nodejs to debug the NodeJs REST API.

 

Configuring the client code to use Asp.Net Core 1.0 or NodeJs REST Service endpoint

To configure the client code to user Asp.Net Core 1.0 or NodeJs REST Service endpoint, we’ll use the “baseApiUrl” variable under client/src/common/config.ts file. To use Asp.Net Core 1.0 REST Service endpoint, use this:

public static baseApiUrl = "http://localhost:5000/api/";

To use Asp.Net Core 1.0 REST Service endpoint, use this:

public static baseApiUrl = http://localhost:8080/api/;

 

Changing the implementation of Delete endpoint in Asp.Net core to take a JSON object

In my first implementation, I was sending the id of the contact to be deleted in the HTTP body to the Delete endpoint. Let us change it to a JSON object like this:

{ id: 1 }

Let us define a model class named DeletePayload under Models folder like this:

namespace Modern.Models
{
    public class DeletePayload
    {
        public int id { get; set; }
    }
}

Change the signature and implementation of Delete() method in ContactsController to take a DeletePayload object like this:

[HttpDelete]  
public IActionResult Delete([FromBody] DeletePayload payload) 
{
    _contactsRepository.Delete(payload.id);
    return new NoContentResult();
} 

 

Changing the implementation of delete method in ContactService class

Let us change the implementation of delete method in ContactService under client/src/services/ to send the deletePayload json object like this:

delete(id: number) {
    this.isRequesting = true;
    return new  Promise( async (resolve: any) => {
            const http = this.httpClient;
            let deletePayload = { "id": id};
            const response = await http.fetch("contacts", {
              method: "delete",
              body: json(deletePayload)
            });
            let justDeleteContact =  (await response.json());
            resolve(justDeleteContact);
            console.log(justDeleteContact);
            this.isRequesting = false;
        });
  }

Running the Aurelia client app with Asp.Net Core and NodeJs

Since we have launched 2 instances of VS Code, you can run the REST application using F5 key. Now, both instances of VS Code will be waiting to serve the requests coming from the client application. First, you can change the port number of baseApiUrl variable in config.ts to 8080 to use NodeJs. You can launch the client application from the browser using the url http://localhost:9000 . You can test different endpoints from the UI by selecting a contact, removing a contact, modifying a contact and by adding a new contact. After you have tested the NodeJs REST API endpoints, you can switch the port number of baseApiUrl variable in config.ts to 5000 to use Asp.Net Core 1.0. You’ll repeat the same set of operations from the UI to test the Asp.Net Core 1.0 REST API endpoints.

 

Conclusion

In section 3 of part 6, we have seen how to bring both Asp.Net Core 1.0 and NodeJs backend REST Service implementation into a single folder structure and leveraging the same Aurelia client application source code. Now, we can debug Asp.Net Core 1.0 and NodeJs backend REST Service implementation from 2 instances of VS Code but leveraging the same client side code. We are going to use both Asp.Net Core 1.0 and NodeJs backends in the future parts. This section concludes part 6. Happy Coding!

Thursday, October 20, 2016

Building a web application using ASP.NET Core 1.0, Node.Js, Express/Hapi/Restify, TypeScript and Visual Studio Code - Part 6, Sec 2

 

Introduction

YouTube Video Link for Part 6, Section 1:

YouTube Video Link for Part 6, Section 2:

This is the second section of the sixth part of Building a web application using ASP.NET Core 1.0, Node.Js, Aurelia, TypeScript, Webpack in Visual Studio Code.

The source code for this tutorial is available on GitHub.

In this part, I’m going to show how to extract the routes from the Application class into its own router class. Then, I’ll show to use Aurelia’s dependency injection container to inject the dependencies so we don’t have to create the objects explicitly using new operator.

 

Important Changes

There are a couple of important changes since I posted the last section of part 6 of this series. Node.Js v6.9.0 LTS is now available for download at https://nodejs.org/en/.  VS Code 1.6.1 is now available for downloat ad https://code.visualstudio.com/download . Aurelia now has a VS Code plugin, which support syntax highlighting and code completion. Check out the VS Code Aurelia plugin announcement page at http://blog.aurelia.io/2016/10/11/introducing-the-aurelia-vs-code-plugin/ To install the plugin, press CTRL + P from within VS Code and use the following command:

ext install aurelia

vscode-install-aurelia-plugin

After you hit Enter key, you’ll see the aurelia extensions in the “EXTENSIONS” pane and select “aurelia 0.1.5” and click “Install” button. After the installation is completed, click “Enable” button to enable the VS Code Aurelia plugin. VS Code will ask you to restart VS Code and select “OK”.

vscode-aurelia-plugin

Please make sure to install Node.Js v6.9.0 LTS, VS Code 1.6.1 and VS Code Aurelia plugin.

Refactoring the routes in all Application classes into their own classes

The application class for each of those 3 frameworks is also defining the routes and the call back functions. It makes sense to refactor the routes into their own classes. Let us create a folder named routes and create routes classes for Express, Hapi and Restify frameworks. While we do this refactoring, let us also add the methods to handle POST, PUT and DELETE HTTP verbs to fully expose the CRUD operation as APIs.

 

Creating a router class for Express

After wiring the body parser in the pipeline, let us add the post() method that can receive a contact in http body as json that can be persisted on the back end. Let us also add the put() and delete() methods that can modify a contact and delete a contact respectively.

Let us create a class named ExpressContactRouter and move the routing methods configApiRoutes() and configErrorRoutes() method from ExpressApplication into ExpressContactRouter like this:

express-contact-router.ts
"use strict";
import * as express from "express";
import * as bodyParser from "body-parser";
import {IContact} from "../models/icontact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

export class ExpressContactRouter {
    private contactRouter: express.Router;
    private contactService: InMemoryContactService;

    constructor() {
        this.contactService = new InMemoryContactService();
        this.contactRouter = express.Router();
    }

    configErrorRoutes(expressApplication: express.Application) {
         // catch 404 error
        expressApplication.use(function(request: express.Request, response: express.Response, next: express.NextFunction) {
            response.status(404).send("Not Found");
        });

        // catch 500 error (Internal Server Error)
        expressApplication.use(function(err: any, request: express.Request, response: express.Response, next: express.NextFunction) {
        console.log("Application Error");
        response.sendStatus(500);
        });
    }
    configApiRoutes(expressApplication: express.Application) {
        this.contactRouter.route("/contacts")
        .get((request: express.Request, response: express.Response) => {
           this.contactService.getAll().then((contacts : IContact[]) => {
                response.json(contacts);
            });
        })
        .post((request: express.Request, response: express.Response) => {
            this.contactService.save(request.body).then((contact: IContact) => response.json(contact));
        })
        .put((request: express.Request, response: express.Response) => {
            this.contactService.save(request.body).then((contact: IContact) => response.json(contact));
        })
        .delete((request: express.Request, response: express.Response) => {
            this.contactService.delete(parseInt(request.body.id, 10)).then((contact: IContact) => response.json(contact));
        });
        expressApplication.use("/api", bodyParser.json());
        expressApplication.use("/api", this.contactRouter);
    }
}

 

We can now remove the unwanted import statements, unwanted methods configApiRoutes(), configApiRoutes(), add the import statement for ExpressContactRouter , remove the route objects and add the ExpressContactRouter property like this:

"use strict";
import * as express from "express";
import * as debug from "debug";
import * as http from "http";
import {INodeJsApplication} from "./inodejs-application";
import {ExpressContactRouter} from "../routes/express-contact-router";

export class ExpressApplication implements INodeJsApplication {
  private expressApplication: express.Application;
  private expressContactRouter: ExpressContactRouter;

Let us change the constructor of ExpressContactRouter to invoke the routing methods configApiRoutes() and configErrorRoutes() in ExpressContactRouter like this: 

constructor() {
    // create expressjs application
    this.expressApplication = express();
    // create express contact routes
    this.expressContactRouter = new ExpressContactRouter();
    // configure API and error routes
    this.expressContactRouter.configApiRoutes(this.expressApplication);
    this.expressContactRouter.configErrorRoutes(this.expressApplication);
}

 

Creating a router class for Hapi

Let us add the post() method that can receive a contact in http body as json that can be persisted on the back end. Let us also add the put() and delete() methods that can modify a contact and delete a contact respectively.

Let us create a class named HapiContactRouter and move the routing methods configApiRoutes() and configErrorRoutes() method from HapiApplication into HapiContactRouter like this:

hapi-contact-router.ts
"use strict";
import * as Hapi from "hapi";
import * as Boom from "Boom";
import {IContact} from "../models/icontact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

export class HapiContactRouter {
  private contactService: InMemoryContactService;
  constructor() {
    console.log("HapiApplication ctor");
    this.contactService = new InMemoryContactService();
  }

  public configErrorRoutes( hapiApplication: Hapi.Server) {
    // catch 404 and forward to error handler
     hapiApplication.ext("onPreResponse", (request: Hapi.Request, reply: Hapi.IReply) => {
       if(request.response.isBoom) {
         if(request.response.output.statusCode === 404) {
           console.log("Invalid Url");
            return reply(Boom.notFound("check your url"));
         }
         return reply(request.response);
       }
       return reply.continue();
    });
  }

  public configApiRoutes(hapiApplication: Hapi.Server) {
      var endpoint = "/api/contacts";
        hapiApplication.route({
            path: endpoint,
            method: "GET",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
               this.contactService.getAll().then((contacts : IContact[]) => {
                  reply(contacts);
               });
            }});
        hapiApplication.route({
            path: endpoint,
            method: "POST",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
                this.contactService.save(request.payload).then((contact : IContact) => {
                reply(contact);
            });
        }});
        hapiApplication.route({
            path: endpoint,
            method: "PUT",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
                this.contactService.save(request.payload).then((contact : IContact) => {
                reply(contact);
            });
        }});
        hapiApplication.route({
            path: endpoint,
            method: "DELETE",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
                this.contactService.delete(parseInt(request.payload.id, 10)).then((contact : IContact) => {
                reply(contact);
            });
        }});
    }
}

 

We can now remove the unwanted import statements, unwanted methods configApiRoutes(), configApiRoutes(), add the import statement for HapiContactRouter , remove the route objects and add the HapiContactRouter property like this: 

"use strict";
import * as Hapi from "hapi";
import * as Boom from "Boom";
import {INodeJsApplication} from "./inodejs-application";
import {HapiContactRouter} from "../routes/hapi-contact-router";

export class HapiApplication implements INodeJsApplication {
  public hapiApplication: Hapi.Server;
  private hapiContactRouter: HapiContactRouter;

 

Let us change the constructor of HapiContactRouter to invoke the routing methods configApiRoutes() and configErrorRoutes() in HapiContactRouter like this:

 
constructor() {
    console.log("HapiApplication ctor");
    // create expressjs application
    this.hapiApplication = new Hapi.Server();
    this.hapiContactRouter = new HapiContactRouter();
  }

 

Creating a router class for Restify

Let us add the post() method that can receive a contact in http body as json that can be persisted on the back end. Let us also add the put() and delete() methods that can modify a contact and delete a contact respectively.

Let us create a class named RestifyContactRouter and move the routing methods configApiRoutes() and configErrorRoutes() method from RestifyApplication into RestifyContactRouter like this:

restify-contact-router.ts
"use strict";
import * as restify from "restify";
import {IContact} from "../models/IContact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

export class RestifyContactRouter {
    private contactService: InMemoryContactService;
    constructor() {
        console.log("RestifyContactRoutes ctor");
        this.contactService = new InMemoryContactService();
    }

 public configErrorRoutes(restifyApplication: restify.Server) {
    // catch 404 and forward to error handler
    restifyApplication.on("NotFound", function(request: restify.Request, response: restify.Response,
     erorr: restify.HttpError, next: restify.Next) {
      console.log("Invalid Url");
      var customError = new Error("Not Found");
      response.send(404, customError);
      return next();
    });
  }

  public configApiRoutes(restifyApplication: restify.Server) {
      var endpoint = "/api/contacts";
        restifyApplication.get(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
           this.contactService.getAll().then((contacts : IContact[]) => {
                response.json(contacts);
                return next();
            });
        }));

        restifyApplication.post(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
            this.contactService.save(request.body).then((contact : IContact) => response.json(contact));
            return next();
        }));

        restifyApplication.put(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
            this.contactService.save(request.body).then((contact : IContact) => response.json(contact));
            return next();
        }));

        restifyApplication.del(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
            this.contactService.delete(parseInt(request.body.id, 10)).then((contact : IContact) => response.json(contact));
            return next();
        }));
  }
}

 

We can now remove the unwanted import statements, unwanted methods configApiRoutes(), configApiRoutes(), add the import statement for RestifyContactRoutes , remove the route objects and add the RestifyContactRouter property like this: 

"use strict";
import * as restify from "restify";
import {INodeJsApplication} from "./inodejs-application";
import {RestifyContactRouter} from "../routes/restify-contact-router";

export class RestifyApplication implements INodeJsApplication {
  private restifyApplication: restify.Server;
  private restifyContactRouter: RestifyContactRouter;

Let us change the constructor of RestifyContactRouter to invoke the routing methods configApiRoutes() and configErrorRoutes() in RestifyContactRouter like this:

constructor() {
    console.log("RestifyApplication ctor");
    // create restify server
    this.restifyApplication = restify.createServer();
    restify.CORS.ALLOW_HEADERS.push("authorization");
    this.restifyApplication.use(restify.CORS());
    this.restifyApplication.use(restify.pre.sanitizePath());
    this.restifyApplication.use(restify.acceptParser(this.restifyApplication.acceptable));
    this.restifyApplication.use(restify.bodyParser());
    this.restifyApplication.use(restify.queryParser());
    this.restifyApplication.use(restify.authorizationParser());
    this.restifyApplication.use(restify.fullResponse());
    // configure contact routes
    this.restifyContactRouter = new RestifyContactRouter();

    // configure API and error routes
    this.restifyContactRouter.configApiRoutes(this.restifyApplication);
    this.restifyContactRouter.configErrorRoutes(this.restifyApplication);
  }

 

Using DI in Node.js by leveraging Aurelia DI Container module

Let us leverage Aurelia’s DI Container in Node.js because the Constructor Injection pattern used in Aurelia is very neat. This would also help us validate that a well designed non-UI or DOM specific client side JavaScript libraries can easily be reused on the JavaScript Server Side platform like Node.js. We don’t need the Aurelia framework to use Aurelia dependency injection module because there is no dependency on the framework from DI container module. Let us install aurelia dependency injection and aurelia polyfills modules using the command:

npm install aurelia-dependency-injection aurelia-polyfills --save

Under the di folder, create a file named aurelia-di-container.ts and add this code:

import "aurelia-polyfills";
import {Container} from "aurelia-dependency-injection";
import {NodeServer} from "../../node-server";


export class AureliaDIContainer {
    static bootstrap() {
        let container = new Container();
        let nodeServer = container.get(NodeServer);
        nodeServer.bootstrap();
    }
}

AureliaDIContainer.bootstrap();

 

In lines 6-9, we define a static method named bootstrap(), construct an instance of Aurelia’s Container class, then get an instance of NodeServer class and invoke its bootstrap method. In line 13, we invoke that static bootstrap(0 of AureliaDIContainter class.

After defining AureliaDIContainer, we need to change the “program” setting in launch.json to point to aurelia-di-containter.ts like this:

"program": "${workspaceRoot}/server/src/contact-manager/di/aurelia-di-container.ts"()

 

Refactoring NodeJsFrameworkFactory using Aurelia DI

Instead of creating the NodeJs Application classes using new operator, let us inject the instances into NodeJsFrameworkFactory using aurelia’s @inject decorator. When we use @inject decorator, we need to make sure that we have the constructor signature match the list of dependencies in the inject decorator. Decorators is an experimental feature in ECMAScript 7 and in TypeScript. So, we need to enable that feature in tsconfig.json using this setting:

“experimentalDecorators”: true
nodejs-framework-factory.ts
import {inject} from "aurelia-dependency-injection";
import {ExpressApplication} from "./express-application";
import {RestifyApplication} from "./restify-application";
import {HapiApplication} from "./hapi-application";
import * as _ from "lodash";

@inject(ExpressApplication, RestifyApplication, HapiApplication)
export class NodeJsFrameworkFactory {
    private frameworkInstances: any;
    constructor(private expressApplication: ExpressApplication, private restifyApplication: RestifyApplication,
    private hapiApplication: HapiApplication) {
        console.log("NodeJsFramework ctor");
        this.frameworkInstances = {};
        for(let argument of arguments) {
            let typeName:string = _.startCase(argument.constructor.name);
            this.frameworkInstances[`${typeName} Application`] = argument;
        }
  }
    public createNodeJsFramework(restFramework: string) : any {
        return (restFramework) ? this.frameworkInstances[_.startCase(`${restFramework} Application`)] :
            this.frameworkInstances["Express Application"];
    }
}

In line 7, we inject the dependencies ExpressApplication, RestifyApplication and HapiApplication using inject decorator. In line 10, we define the properties that match the list of dependencies in the inject decorator. From line 13 to 16, we create an array to hold the instances of the Application classes pertaining to the three NodeJs frameworks. We enumerate the function’s arguments collection, which in this case would be three that matches the dependencies in the inject decorator. We get the name of the class using argument.constructor.name to use as the key into the frameworkInstances object.

 

Refactoring NodeServer to inject NodeJsFrameworkFactory

Let us refactor NodeServer class to inject NodeJsFrameworkFactory and remove the code that instantiates nodeServer and invoke nodeServer.bootstrap() since that is now done in AureliaDIContainer class.

node-server.ts
import {inject} from "aurelia-dependency-injection";
import {NodeJsFrameworkFactory} from "./contact-manager/bootstrap/nodejs-framework-factory";

@inject(NodeJsFrameworkFactory)
export class NodeServer {
  constructor(private nodejsFrameworkFactory : NodeJsFrameworkFactory) {
    console.log("NodeServer ctor");
  }

 

Refactoring ExpressApplication, RestifyApplication and HapiApplication classes to inject their Router classes

Instead of creating an instance of ExpressContactRouter using new operator, we will inject an instance of ExpressContactRouter into the constructor of ExpressApplication using inject decorator like this:

express-application.ts
import {inject} from "aurelia-dependency-injection";

@inject(ExpressContactRouter)
export class ExpressApplication implements INodeJsApplication {
  private expressApplication: express.Application;

  constructor(private expressContactRouter: ExpressContactRouter) {
    // create expressjs application
    this.expressApplication = express();

    // configure API and error routes   
    this.expressContactRouter.configApiRoutes(this.expressApplication);
    this.expressContactRouter.configErrorRoutes(this.expressApplication);
  }

Instead of creating an instance of HapiContactRouter using new operator, we will inject an instance of HapiContactRouter into the constructor of HapiApplication using inject decorator like this:

hapi-application.ts
import {inject} from "aurelia-dependency-injection";

@inject(HapiContactRouter)
export class HapiApplication implements INodeJsApplication {
  public hapiApplication: Hapi.Server;

  constructor(private hapiContactRouter: HapiContactRouter) {
    console.log("HapiApplication ctor");
    // create expressjs application
    this.hapiApplication = new Hapi.Server();
  }

Instead of creating an instance of RestifyContactRouter using new(), we will inject an instance of RestifyContactRouter into the constructor of RestifyApplication using inject decorator like this:

restify-application.ts
import {inject} from "aurelia-dependency-injection";

@inject(RestifyContactRouter)
export class RestifyApplication implements INodeJsApplication {
  private restifyApplication: restify.Server;

  constructor(private restifyContactRouter: RestifyContactRouter) {
    console.log("RestifyApplication ctor");
    // create restify server
    this.restifyApplication = restify.createServer();
    restify.CORS.ALLOW_HEADERS.push("authorization");
    this.restifyApplication.use(restify.CORS());
    this.restifyApplication.use(restify.pre.sanitizePath());
    this.restifyApplication.use(restify.acceptParser(this.restifyApplication.acceptable));
    this.restifyApplication.use(restify.bodyParser());
    this.restifyApplication.use(restify.queryParser());
    this.restifyApplication.use(restify.authorizationParser());
    this.restifyApplication.use(restify.fullResponse());

    // configure API and error routes
    this.restifyContactRouter.configApiRoutes(this.restifyApplication);
    this.restifyContactRouter.configErrorRoutes(this.restifyApplication);
  }

 

Refactoring ExpressContactRouter, RestifyContactRouter and HapiCotactRouter classes to inject InMemoryContactService

Instead of creating an instance of InMemoryContactService using new operator, we will inject an instance of InMemoryContactService into the constructor of ExpressContactRouter using inject decorator like this:

express-contact-router.ts
import {inject} from "aurelia-dependency-injection";

@inject(InMemoryContactService)
export class ExpressContactRouter {
    private contactRouter: express.Router;

    constructor(private contactService: InMemoryContactService) {
       this.contactRouter = express.Router()     }

Instead of creating an instance of InMemoryContactService using new operator, we will inject an instance of InMemoryContactService into the constructor of HapiContactRouter using inject decorator like this:

hapi-contact-router.ts
import {inject} from "aurelia-dependency-injection";

@inject(InMemoryContactService)
export class HapiContactRouter {
  constructor(private contactService: InMemoryContactService) {
    console.log("HapiApplication ctor");
  }

Instead of creating an instance of InMemoryContactService using new operator, we will inject an instance of InMemoryContactService into the constructor of RestifyContactRouter using inject decorator like this:

restify-contact-router.ts
import {inject} from "aurelia-dependency-injection";

@inject(InMemoryContactService)
export class RestifyContactRouter {
    constructor(private contactService: InMemoryContactService) {
        console.log("RestifyContactRoutes ctor");
    }

You can run the application using all 3 frameworks now using the REST_FRAMEWORK environment variable in launch.json. To test Express, Hapi and Restify frameworks, we need to just change the REST_FRAMEWORK to “express” or "hapi" or "restify" in launch.json

 

Using Aurelia DI Resolvers

If we don’t want to create the instances eagerly, which is the default resolver used by Aurelia internally, we can explicitly specify which resolver to use to provide the instances. The resolver that we could use is Lazy, which injects a function for lazily evaluating the dependency. The DI resolvers are part of aurelia framework, which we should install using the command:

npm install aurelia-framework --save

Instead of injecting the actual instance, we’ll inject the resolver Lazy.of(type), which will inject a function that returns the type that can be used later. Let us just modify NodeJsFrameworkFactory to use Lazy.of() resolver like this:

nodejs-framework-factory.ts
import {inject} from "aurelia-dependency-injection";
import {Lazy} from "aurelia-framework";
import {ExpressApplication} from "./express-application";
import {RestifyApplication} from "./restify-application";
import {HapiApplication} from "./hapi-application";
import * as _ from "lodash";

@inject(Lazy.of(ExpressApplication), Lazy.of(RestifyApplication), Lazy.of(HapiApplication))
export class NodeJsFrameworkFactory {
    private frameworkInstances: any;
    constructor(private getExpressApplication : () => ExpressApplication,
    private getRestifyApplication: () => RestifyApplication,
    private hapiApplication : () => HapiApplication) {
        console.log("NodeJsFramework ctor");
        let frameworks = ["Express Application", "Restify Application", "Hapi Application"];
        this.frameworkInstances = {};
        let i = 0;
        for(let argument of arguments) {
            let typeName:string = frameworks[i++];
            this.frameworkInstances[`${typeName}`] = argument;
        }
  }
    public createNodeJsFramework(restFramework: string) : any {
        return (restFramework) ? this.frameworkInstances[_.startCase(`${restFramework} Application`)] ():
            this.frameworkInstances["Express Application"] ();
    }
}

 

Conclusion

In section 2 of part 6, we have seen how to extract the routes from the NodeJsApplication classes for the 3 popular NodeJs Web frameworks: Express, Hapi Restify and create router classes for each of them. Then, we have seen how to use Aurelia’s DI container to inject the dependencies via constructor using @inject decorator instead of constructing them explicitly using new operator. We have also seen how to use Lazy resolver to defer the creation until it is required using a function returned by DI instead of an instance itself.

In section 3 of part 6, I’ll show you how you can have both ASP.NET Core 1.0 and NodeJs backend REST APIs and make them work with the same Aurelia front end web application in a single folder structure but using 2 instances of Visual Studio Code without duplicating the Aurelia front end web application source code. Happy Coding!

Saturday, October 8, 2016

Building a web application using ASP.NET Core 1.0, Node.Js, Express/Hapi/Restify, TypeScript and Visual Studio Code - Part 6, Sec 1

 

Introduction

YouTube Video Link for Part 6, Section 1:

This is the first section of the sixth part of Building a web application using ASP.NET Core 1.0, Node.Js, Aurelia, TypeScript, Webpack in Visual Studio Code.

The source code for this tutorial is available on GitHub.

In this part, I’m going to show how to quickly setup Node.Js, TypeScript, Visual Studio Code and create a REST service using them. We are going to build a ContactManager REST service API in Node.Js using the three popular NodeJs Web frameworks: Express, Hapi and Restify, which we’ll consume from a modern SPA application built using Aurelia, TypeScript and WebPack. I’ll also show you how you can leverage Aurelia’s DI container on the server side inside NodeJs as well as how we can re-use our client side repository interface and InMemoryRepository implementation on the server side.

Installing the Pre-requisites

Before we start installing the pre-requisites, let us create a folder structure to start building our Node.JS REST API Service. Create a folder structure using this command:

mkdir C:\tutorial\part6\modern\nodejs\server\src

and then set the directory as the current directory using this command:

cd C:\tutorial\part6\modern\nodejs\

To install Node.Js, visit nodejs.org and download the Current version. At the time of writing this blog post, the Current version of Node.js is v6.6.0. Node.js is built using Chrome’s V8 JavaScript engine. Microsoft created a project that enables Node.js to optionally use the Microsoft’s ChakraCore JavaScript engine, which is powerful. Checkout the github page of Node.js on ChakraCore here. If you don’t want to spend time compiling Node.js with ChakraCore source code, you can download the releases from here.

Node.js comes with a powerful package manager named NPM (Node Package Manager). We can use npm to install both server and client JavaScript libraries. It is always a good idea to update npm using npm :) It might sound funny but I really mean it because npm has frequent releases than Node.js itself. To update npm, run the command:

npm install npm --g

Before we start installing other node modules, let us create a package.json by running this command:

npm init -y

Since we are going to use TypeScript to develop Node.Js applications, let us install TypeScript 2.0 RTM Release by running the command:

npm install typescript@2.0.3 --g
npm install typescript@2.0.3  --save-dev

Since TypeScript requires the type definition files for JavaScript libraries, let us install the typings tool by running the command:

npm install typings --g

Let us create typings.json using this command:

typings init

Let us create a tsconfig.json file that will contain the settings required by TypeScript compiler. To create tsconfig.json, run the command:

tsc --init

To develop a REST service, we need a good web framework. Express is a fast, unopinionated, minimalist web framework for Node.Js. To install express, please run the command

npm install express body-parser --save

Now, let us install the type definitions required for Express by running the following command:

typings install dt~express dt~serve-static dt~express-serve-static-core dt~mime dt~debug dt~body-parser --save --global

Hapi is another rich framework for building applications and services in Node.js. To install hapi, please run the command:

npm install hapi boom @types/hapi @types/boom --save

Note: Many node modules ship their type definitions in @types namespace and hence you can use npm install @types/<module_name> instead of typings install dt~module_name.

Restify is a very popular NodeJs framework and focus only on providing REST service endpoints unlike Express, which is a full blown web framework that you could use to serve side web pages as well as REST service endpoints. To install restify, please run the command:

npm install restify bunyan --save

Now, let us install the type definitions required for Restify by running the following command:

typings install dt~restify dt~bunyan --save --global

Since we’ll need some string manipulation functions from lodash, let us install lodash using the command:

npm install lodash @types/lodash --save

Creating Project Structure in Visual Studio Code

After we installed the pre-requisites, let us launch VS Code by changing to the project directory in Cmd.exe or Cmder (I prefer the latter) and by running the command:

code .

Make sure to  modify the settings in tsconfig.json to look like this:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6",
        "moduleResolution": "node",
        "noImplicitAny": false,
        "sourceMap": true,
        "watch": true    
    },
    "exclude" : [
        "node_modules"
    ]
}

It is time to configure the Task Runner for “TypeScript –tsconfig.json” project type by typing Ctrl + Shift + B and follow the on-screen instructions below:

config-ts-taskrunner

ts-tsconfig.json-taskrunner

After we click on “Configure Task Runner” button and select “TypeScript – tsconfig.json Compiles a TypeScript project” as our Task Runner, VSCode will generate a tasks.json specific to this project type. We don’t need to modify the default values because they are good enough for our project. Once we configure the Task Runner, Ctrl+Shift+B is the key to remember to start/stop i.e., it acts as a toggle command to start the task runner.

Invoke the task runner by clicking Ctrl+Shift+B and you should see that the TypeScript compiler will be happy to compile without any errors and watching the file system for any changes you make to compile the modified files like this:

output-task-runner

Now, let us start creating our application by adding a folder named “src” and a file named index.ts like this:

src-index

As soon as you added the file index.ts, our task runner which is watching for changes, transpiles index.ts into index.js and index.js.map since we chose to generate source maps as well. While it is great to see the generated JavaScript source and source map files to understand that the TypeScript compiler is working, it becomes annoying over a period of time to see 2 extra files in the folder for each .ts file you create. Visual Studio has a nice feature to hide the unwanted files using “File | Preferences | Workspace Settings” menu option. When you select that menu option, we should see a settings.json file under .vscode folder.

file-preferences-workspace-settings 

To make VS Code use TypeScript 2.0.3, add this setting in .vscode\settings.json file:

"typescript.tsdk": "node_modules/typescript/lib"

We need to add the “files.exclude” setting to hide .js and .js.map files:

// Place your settings in this file to overwrite default and user settings.
{
   "typescript.tsdk": "node_modules/typescript/lib",

   // Files

    // Configure glob patterns for excluding files and folders.
   "files.exclude": {
        "**/*.map": {"when": "$(basename)"},
        "**/*.js": {"when": "$(basename).ts"}
    }
}

After you make the changes in settings.json, in order for VS Code’s language service to use TypeScript 2.0.3 installed locally under node_modules/typescript/lib folder, you need to restart VS Code.

If you are interested to learn about all other User and Workspace Settings, please visit the VSCode documentation page at https://code.visualstudio.com/docs/customization/userandworkspace

I don’t like to see the src folder messed up with bunch of .js and .js.map files alongside .ts files. I’d like to keep it clean and also be able to use another editor/IDE like Visual Studio 2015 or 15 to run the project. So, a better approach to organize src and output files is to create a dist folder and configure TypeScript compiler to output the .js and .js.map files into the dist folder using “outDir” setting inside tsconfig.json like this:

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6",
        "noImplicitAny": false,
	...
	"outDir": "server/dist"
     },
     ...
}

 

We need to make some changes to .vscode/launch.json in order for VS Code debugger to pickup the right source map files so our breakpoints would work just fine. In launch.json , under “configurations” settings, we need to modify “program”, “sourceMaps” and “outDir” settings like this:

"configurations": [
        {
            "name": "Launch",
            "type": "node",
            "request": "launch",
            "program": "${workspaceRoot}/server/src/node-server.ts",
            "stopOnEntry": false,
            "args": [],
            "cwd": "${workspaceRoot}",
            "preLaunchTask": null,
            "runtimeExecutable": null,
            "runtimeArgs": [
                "--nolazy"
            ],
            "env": {
                "NODE_ENV": "development"
            },
            "console": "internalConsole",
            "sourceMaps": true,
            "outDir": "${workspaceRoot}/server/dist"
        }

 

Setting up launch.json to run our Node.Js REST service from VS Code

To run an application from VS Code, simply hit F5 and in the “Select Environment”, choose Node.js. VS Code will create a launch.json under .vscode folder with the configuration required to launch a NodeJs application.

launch-select-environment

Open launch.json under the folder .vscode and change the “program” property to point to “server.ts” and and “sourceMaps” property to true. If you don’t make these changes, you can spend hours or days to figure out why aren’t our breakpoints not working. Our launch.json should look like this:

launch-important-settings

Creating a Contacts REST API using Express, Hapi and Restify frameworks

Since we’ll be creating this application class for all 3 frameworks: express, hapi and restify, let us define a TypeScript interface name INodeJsApplication under the folder named “bootstrap”.

inodejs-application.ts

export interface INodeJsApplication {
    bootstrap(port: number);
}

 

Creating a GET All Contacts API endpoint in Express

 

express-application.ts

"use strict";
import * as express from "express";
import * as debug from "debug";
import * as http from "http";
import {INodeJsApplication} from "./inodejs-application";

export class ExpressApplication implements INodeJsApplication {
  public expressApplication: express.Application;
  private contactRouter: express.Router;
  constructor() {
    // create expressjs application
    this.expressApplication = express();
    // create expressjs router
    this.contactRouter = express.Router();

    // configure application routes
    this.configErrorRoutes();
    this.configApiRoutes();
  }

  private configErrorRoutes() {
    // catch 404 error
    this.expressApplication.use(function(err: any, request: express.Request, response: express.Response, next: express.NextFunction) {
      console.log("Invalid Url");
      response.sendStatus(404);
    });
  }

  private configApiRoutes() {
    this.contactRouter = express.Router();
    this.contactRouter.route("/contacts")
        .get((request: express.Request, response: express.Response) => {
            let id = 0;

            function getId() {
            "use strict";
            return ++id;
            }

            let contacts = [
            {
                id:getId(),
                firstName:"Raja",
                lastName:"Mani",
                email:"rmani@gmail.com",
                phoneNumber:"408-973-5050",
                birthDate: new Date(1973, 5, 1)
            },
            {
                id:getId(),
                firstName:"Jhansi",
                lastName:"Rani",
                email:"jrani@gmail.com",
                phoneNumber:"867-5309",
                birthDate: new Date(1970, 5, 24)
            },
            {
                id:getId(),
                firstName:"Aditi",
                lastName:"Raja",
                email:"araja@gmail.com",
                phoneNumber:"408-973-9006",
                birthDate: new Date(2001, 10, 12)
            },
            {
                id:getId(),
                firstName:"Mahati",
                lastName:"Raja",
                email:"mraja@gmail.com",
                phoneNumber:"408-973-8007",
                birthDate: new Date(2006, 2, 15)
            }
            ];
            response.json(contacts);
        });
    this.expressApplication.use("/api", this.contactRouter);
  }

  public bootstrap(port: number) {
    this.expressApplication.set("port", port);

    // create http server
    let server = http.createServer(this.expressApplication);
    server.listen(port);
    // add error handler
    server.on("error", onError);

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

    // event listener for HTTP server "error" event.
    function onError(error: any) {
        "use strict";
        if (error.syscall !== "listen") {
            throw error;
        }

        let 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() {
        let addr = server.address();
        let bind = typeof addr === "string"
        ? "pipe " + addr
        : "port " + addr.port;
        let debugForExpress = debug("ExpressApplication");
        debugForExpress("Listening on " + bind);
    }
  }
}

Let us create the node-server.ts, which is a very standard code to start a http server influenced by the Google Cloud Platform source code at https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/appengine/express/bin/www 

node-server.ts

import {ExpressApplication} from "./contact-manager/bootstrap/express-application";

export class NodeServer {
  constructor() {
    console.log("NodeServer ctor");
  }

  private normalizePort(val: string) : any {
      "use strict";
      let port = parseInt(val, 10);

      if (isNaN(port)) {
        // named pipe
        return val;
      }
      if (port >= 0) {
        // port number
        return port;
      }
      return false;
  }

  bootstrap() {
    // get port from environment and store in Express.
    let port = this.normalizePort(process.env.PORT || 8080);
    let restFramework = process.env.REST_FRAMEWORK || "express";
    let server = new ExpressApplication();

    server.bootstrap(port);
  }
}

let nodeServer = new NodeServer();
nodeServer.bootstrap();

 

Debugging our REST service endpoint in VSCode

Now that we have completed application.ts and server.ts, let us try running it from within VS Code. Let us set a breakpoint on line 75 in application.ts that reads “response.json(contacts);”. We can hit f5 and that should launch the debugger in VS Code using launch.json settings. Then, open up chrome browser and enter http://localhost:8085/api/contacts to see the control comes to the breakpoint inside of VS Code and you can inspect the variables by a simple hover on any variable or switching to the Debug View by pressing Ctrl+Shift+D to inspect variables, call stack etc. like this:

vscode-debug-view-breakpoints

Once we hit Continue i.e., F5, the function will return the JSON value of our contacts array with 4 contact objects. We can see the JSON returned from our API inside of the browser like this:

json-result-from-api-contacts

Organizing our project for cleaner separation

It is not a good idea to create a single Application class and bloat the class with our API implementation methods, routes etc. If you noticed, our get() method creates an array of contacts, which we’ve already implemented using an InMemoryContactsRepository on the client side. Since NodeJs can leverage the client side JavaScript code that we’ve written provided we didn’t do any DOM manipulation or UI specific code, we can use that InMemoryContactsRepository on the server side as well.

Let us organize our project for a cleaner separation. The first and foremost thing is defining our application Model classes that are like our business entities. For a Contacts REST API, it is a no-brainer that we need to define the Contact model. We are going to leverage the power of TypeScript interface to define the Contact Model. We’ll create a folder structure for our Contacts REST API like this:

contact-manager-folder-structure

Then, we’ll copy the client side code under “models” and “services” folders into the server side. Here’s the definition of Under models folder, define IContact like this:

icontact.ts

export interface IContact {
    id: number;
    firstName: string;
    lastName: string;
    email: string;
    phoneNumber: string;
    birthDate: Date;
    thumbnailImage?: string;
}  

The next step in organizing our project is to create a “services” folder and add the interface and classes that will wrap our business logic. For this project, we will define IContactService interface like this:

icontact-service.ts

import {IContact} from "../models/icontact";

export interface IContactService {
    getAll();
    get(id: number);
    search(keyword: string);
    save(contact: IContact);
    create();
    delete(id: number);
}

Before we start defining the concrete implementation classes implementing IContactService, let us see the importance of Promise classes in modern JavaScript applications.

Using Promises in Node.js

In JavaScript, when we call a method that runs asychronously i.e., a method that performs an operation for longer time but returns right away and eventually after it is successfully completed or failed would call the success and error callback functions. We can write a complicated and messy code by adding callbacks, which is commonly called as “callback hell”. In ES6, they have introduced Promise classes that makes our asynchronous code look like synchronous code with flat indentation and one exception channel (i.e., error handling code). Nodejs does not support ES6 promises yet. So we can use TypeScript to bring in ES6 Promise classes or better yet use the powerful Bluebird Promise library for all of our Promise implementations. Since the repository code we brought from the client side already uses Bluebird, we are going to use the Bluebird Promise library on the server side as well. Let us install Bluebird and its type definition using this command:

npm install bluebird @types/bluebird --save 

Let us look at a simple in memory implementation of IContactService that we brought from our client side code:

in-memory-contact-service.ts

import {IContact} from "../models/icontact";
import {IContactService} from "./icontact-service";
var Promise = require("bluebird");
let latency = 200;
let id = 0;

function getId(){
  "use strict";
  return ++id;
}

let contacts = [
  {
    id:getId(),
    firstName:"Raja",
    lastName:"Mani",
    email:"rmani@gmail.com",
    phoneNumber:"408-973-5050",
    birthDate: new Date(1973, 5, 1)
  },
  {
    id:getId(),
    firstName:"Jhansi",
    lastName:"Rani",
    email:"jrani@gmail.com",
    phoneNumber:"867-5309",
    birthDate: new Date(1970, 5, 24)
  },
  {
    id:getId(),
    firstName:"Aditi",
    lastName:"Raja",
    email:"araja@gmail.com",
    phoneNumber:"408-973-9006",
    birthDate: new Date(2001, 10, 12)
  },
  {
    id:getId(),
    firstName:"Mahati",
    lastName:"Raja",
    email:"mraja@gmail.com",
    phoneNumber:"408-973-8007",
    birthDate: new Date(2006, 2, 15)
  }
];

export class InMemoryContactService implements IContactService {
  isRequesting = false;

  constructor() {
    console.log("InMemoryContactService ctor");
  }
  get(id: number) {
    this.isRequesting = true;
    return new Promise((resolve: any, reject: any) => {
      setTimeout(() => {
        let found = contacts.filter( (c: IContact) => c.id === id)[0];
        if(found) {
        resolve(JSON.parse(JSON.stringify(found)));
        } else {
          reject("contact not found");
        }
        this.isRequesting = false;
      }, latency);
    });
  }

  getAll() {
    this.isRequesting = true;
    return new Promise( (resolve: any) => {
      setTimeout(() => {
        let results = contacts;
        resolve(results);
        this.isRequesting = false;
      }, latency);
    });
  }

  search(keyword: string) {
    this.isRequesting = true;
    return new Promise((resolve: any) => {
      setTimeout(() => {
        let results = contacts.filter((c: IContact) => ((c.firstName.indexOf(keyword) !== -1) ||
        (c.lastName.indexOf(keyword) !== -1)));
        resolve(results);
        this.isRequesting = false;
      }, latency);
    });
  }

  save(contact: IContact) {
    this.isRequesting = true;
    return new Promise((resolve: any) => {
      setTimeout(() => {
        let instance = JSON.parse(JSON.stringify(contact));
        let found = contacts.filter((c: IContact) => c.id === contact.id)[0];

        if(found) {
          let index = contacts.indexOf(found);
          contacts[index] = instance;
        } else {
          instance.id = getId();
          contacts.push(instance);
        }

        this.isRequesting = false;
        resolve(instance);
      }, latency);
    });
  }
  create() {
    this.isRequesting = true;
    return new Promise((resolve: any) => {
      setTimeout(() => {
        let newContact = {
          id: getId(),
          firstName:"",
          lastName:"",
          email:"",
          phoneNumber:"",
          birthDate: null
        };
        contacts.push(newContact);
        this.isRequesting = false;
        resolve(newContact);
      }, latency);
    });
  }

  private getContactIndex(id: number) : number {
       return contacts.findIndex((c:IContact) => c.id === id);
    }

  delete(id: number) {
    this.isRequesting = true;
    return new Promise((resolve: any, reject: any) => {
      setTimeout(() => {
        let found = contacts.filter((c: IContact) => c.id === id)[0];
        if(found) {
          console.log("Deleting Contact " + id);
          var index = this.getContactIndex(id);
          if(index !== -1) {
            contacts.splice(index,1);
            console.log("Deleted Contact " + id);
            console.log(contacts);
            resolve(JSON.parse(JSON.stringify(found)));
          }
        } else {
          reject("contact not found");
        }
        this.isRequesting = false;
      }, latency);
    });
  }
}

InMemoryContactService implements the CRUD (Create, Read, Update and Delete) methods besides the search() method. All these methods on the internal array of contacts in memory.

If you notice, we are using findIndex method, which is defined in Array.prototype and is only available in ES6. This is why set the “target” setting to “es6” in our tsconfig.json.  Let us add another useful setting named “allowJs” into our tsconfig.json like this:

{
    "compilerOptions": {
       ,
        "allowJs": true
    },
    "exclude": [
        "node_modules"
    ]
}

Setting “allowJs” to “true” allows us to pass through JavaScript files that are untyped modules. “allowJs” comes in handy when you are migrating your JavaScript code to TypeScript and would like the TypeScript compiler to compile your js files. Remember that you can’t use type annotations on modules imported without type definition files i.e., d.ts.

Refactoring ExpressApplication to use InMemoryContactService

We need body-parser module to parse the body of the http request to handle  POST, PUT and DELETE http verbs. We have already installed body-parser when we installed express and its dependent modules.

We need to import the bodyParser, IContact and InMemoryContactService classes like this:

import * as bodyParser from "body-parser";
import {IContact} from "../models/icontact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

We need to let express use the bodyparser  inside our configApiRoutes() method in application.ts like this:

this.expressApplication.use("/api", bodyParser.json());

Define a contactService property inside the ExpressApplication constructor like this:

export class ExpressApplication implements INodeJsApplication {
  ...
  private contactService: InMemoryContactService;
  constructor() {
    ...
    this.contactService = new InMemoryContactService();
    ...
  }

Change the implementation of get() method to use contactService like this:

private configApiRoutes() {
   this.expressApplication.use("/api", bodyParser.json());
   this.contactRouter = express.Router();
   this.contactRouter.route("/contacts")
       .get((request: express.Request, response: express.Response) => {
           this.contactService.getAll().then((contacts : IContact[]) => {
               response.json(contacts);
           });
       });
   this.expressApplication.use("/api", this.contactRouter);
}

 

Creating a GET All Contacts API endpoint in Hapi

hapi-application.ts
"use strict";
import * as Hapi from "hapi";
import * as Boom from "Boom";
import {INodeJsApplication} from "./inodejs-application";
import {IContact} from "../models/icontact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

export class HapiApplication implements INodeJsApplication {
  public hapiApplication: Hapi.Server;
  private contactService: InMemoryContactService;
  constructor() {
    console.log("HapiApplication ctor");
    // create expressjs application
    this.hapiApplication = new Hapi.Server();
  }

  private configErrorRoutes() {
    // catch 404 and forward to error handler
    this.hapiApplication.ext("onPreResponse", (request: Hapi.Request, reply: Hapi.IReply) => {
       if(request.response.isBoom) {
         if(request.response.output.statusCode === 404) {
           console.log("Invalid Url");
            return reply(Boom.notFound("check your url"));
         }
         return reply(request.response);
       }
       return reply.continue();
    });
  }

  public configApiRoutes() {
      var endpoint = "/api/contacts";
        this.hapiApplication.route({
            path: endpoint,
            method: "GET",
            handler: (request: Hapi.Request, reply: Hapi.IReply) => {
               this.contactService.getAll().then((contacts : IContact[]) => {
                  reply(contacts);
               });
            }});
  }

  public bootstrap(port: number) {
    this.hapiApplication.connection({port: port});
    // configure error routes
    this.configErrorRoutes();
    // configure API routes
    this.configApiRoutes();
    this.hapiApplication.start(() => {
      console.log("Listening on " + this.hapiApplication.info.uri);
    });
  }
}

 

Creating a GET All Contacts API endpoint in Restify

restify-application.ts
"use strict";
import * as restify from "restify";
import {INodeJsApplication} from "./inodejs-application";
import {IContact} from "../models/icontact";
import {InMemoryContactService} from "../services/in-memory-contact-service";

export class RestifyApplication implements INodeJsApplication {
  private restifyApplication: restify.Server;
  private contactService: InMemoryContactService;

  constructor() {
    console.log("RestifyApplication ctor");
    // create restify server
    this.restifyApplication = restify.createServer();
    restify.CORS.ALLOW_HEADERS.push("authorization");
    this.restifyApplication.use(restify.CORS());
    this.restifyApplication.use(restify.pre.sanitizePath());
    this.restifyApplication.use(restify.acceptParser(this.restifyApplication.acceptable));
    this.restifyApplication.use(restify.bodyParser());
    this.restifyApplication.use(restify.queryParser());
    this.restifyApplication.use(restify.authorizationParser());
    this.restifyApplication.use(restify.fullResponse());
    // configure error routes
    this.configErrorRoutes();
    // configure API routes
    this.configApiRoutes();
  }

  private configErrorRoutes() {
    // catch 404 and forward to error handler
    this.restifyApplication.on("NotFound", function(request: restify.Request, response: restify.Response,
     erorr: restify.HttpError, next: restify.Next) {
      console.log("Invalid Url");
      var customError = new Error("Not Found");
      response.send(404, customError);
      return next();
    });
  }

  public configApiRoutes() {
      var endpoint = "/api/contacts";
        this.restifyApplication.get(endpoint, ((request: restify.Request, response: restify.Response, next: restify.Next) => {
           this.contactService.getAll().then((contacts : IContact[]) => {
                response.json(contacts);
                return next();
            });
        }));
  }

  public bootstrap(port: number) {
    let server = this.restifyApplication;
      // listen on provided ports
      server.listen(port, () => {
        console.log("%s listening at %s", server.name, server.url);
      });
  }
}

 

Wiring all 3 frameworks in node-server.ts

Let us import the HapiApplication and RestifyApplication classes like this:

import {HapiApplication} from "./contact-manager/bootstrap/hapi-application";
import {RestifyApplication} from "./contact-manager/bootstrap/restify-application";

It is time for us to use that restFramework variable we defined inside of bootstrap() method. That variable will check if there is an environment variable named “REST_FRAMEWORK” passed as an argument to Node. If a value is passed, it will use that value; othwerise, it will default to “express”. Let us use a switch..case statement to check the value of restFramework and create an instance of one of the frameworks: express, hapi or restify like this:

bootstrap() {
    // get port from environment and store in Express.
    let port = this.normalizePort(process.env.PORT || 8080);
    let restFramework = process.env.REST_FRAMEWORK || "express";
    let server = null;
    switch(restFramework) {
      case "express":
        server = new ExpressApplication();
        break;
      case "hapi":
        server = new HapiApplication();
        break;
      case "restify":
        server = new RestifyApplication();
        break;
    }

    server.bootstrap(port);
  }

 

Creating a Class Factory to abstract the creation of these 3 frameworks

NodeJsFrameworkFactory.ts
import {ExpressApplication} from "./express-application";
import {RestifyApplication} from "./restify-application";
import {HapiApplication} from "./hapi-application";
import * as _ from "lodash";

export class NodeJsFrameworkFactory {
    private frameworkInstances: any[];
    constructor() {
        console.log("NodeJsFramework ctor");
        this.frameworkInstances = new Array(arguments.length);
        this.frameworkInstances["express Application"] = new ExpressApplication();
        this.frameworkInstances["hapi Application"] = new HapiApplication();
        this.frameworkInstances["restify Application"] = new RestifyApplication();
  }
    public createNodeJsFramework(restFramework: string) : any {
        return (restFramework) ? this.frameworkInstances[_.startCase(`${restFramework} Application`)] :
            this.frameworkInstances["expressApplication"];
    }
}

 

Refactoring NodeServer to use NodeJsFrameworkFactory

We will remove the import statements for ExpressApplication, HapiApplication and RestifyApplication and just import NodeJsFrameworkFactory class in node-server.ts like this:

import {NodeJsFrameworkFactory} from "./contact-manager/bootstrap/nodejs-framework-factory";

We’ll replace the switch..case statement inside bootstrap() method like this:

let server = this.nodejsFrameworkFactory.createNodeJsFramework(restFramework);

 

Changing REST_FRAMEWORK environment variable to test all 3 frameworks

We have completed wiring all 3 popular NodeJs frameworks into our NodeServer class. Now, we can add REST_FRAMEWORK environment variable in launch.json to test all 3 frameworks like this:

launch.jsons
"configurations": [
        { 
		... 
		"env": {
                "NODE_ENV": "development",
                "REST_FRAMEWORK": "express"
            	},
		 ... 
		] 
	}

To test Hapi and Restify frameworks, we need to just change the REST_FRAMEWORK to "hapi" or "restify" in launch.json.

Conclusion

In section 1 of part 6 of the series, we have covered a lot of Node.Js server side concepts and used three popular web frameworks: Express, Hapi and Restify to implement an API endpoint using HTTP GET. In section 2 of part 6 of this series, we’ll see how you can refactor the routes out of the application classes and use DI (Dependency Injection) to inject the NodeJsFrameworkFactory, Application, Service and Route classes and use them from NodeServer class. Happy coding!