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!

No comments:

Post a Comment