2016-06-11

NodeJS, Web-Express, and TypeScript from scratch - Part 1

Over the past year I've been spending my time contracting for a company in Norway. Now that project is completed it is time for me to start playing again, so I thought I'd pick up an old Node Express project. This time I intend to use TypeScript instead of Javascript. I'd also like to write unit tests and dependency injection - both are something I'm very familiar with in the C# world, but not in Node. I'm going to use this blog to record what I did; I will undoubtedly revisit some of these posts and make changes as I learn.

In this first blog I intend to cover how to get up and running with Node, Express, and WebStorm (optional) from a fresh installation of Ubuntu Linux.

The first thing we need to do is use apt-get to install Node.
sudo apt-get install nodejs
I've noticed that some Linux apps will look for a command "node" and others will look for "nodejs", so after installing I want to make an alias so that both commands will work.
cd /usr/bin
sudo ln -s nodejs node
Next we need to install the package manager for Node. This allows us to reference 3rd party libraries (such as the Express web server) in our applications. It also keeps track of which specific versions we have used for different applications we have developed, so it's possible to have different apps running on different library versions and upgrade them independently of each other.
sudo apt-get install npm
With npm installed we can now get start to import our Node dependencies. These dependencies are used for getting a project up and running very quickly and so need to be installed as global packages, which means they will be available anywhere on your Linux installation by using the npm command. To install an npm package globally we need to use the -g option.
sudo npm install -g gulp
sudo npm install -g yo
sudo npm install -g generator-express-typescript
Gulp and Yo are both task automation applications. Gulp is used to compile (transpile) our TypeScript into JavaScript and then run our test web server, we can also use it to run automated unit tests. Yo is used to create the skeleton app that we will develop further, and generator-express-typescript is the template it will use to create it. So, make a folder, cd into it and type
yo express-typescript
Yo will ask if you wish to use gulp or grunt, choose gulp; when asked if you wish to create a Dockerfile select Yes.

Once the skeleton for our new application has been created type
gulp
The gulp command will look for a file named gulpfile.js - this is where we can create named tasks to run from the command line. If no command is specified, gulp will run the default task in the file, which is named 'default'.

You will see output similar to this
[10:51:12] Using gulpfile ~/source/yourapp/gulpfile.js
[10:51:12] Starting 'clean'...
[10:51:13] Finished 'clean' after 1.22 s
[10:51:13] Starting 'ts'...
[10:51:16] Finished 'ts' after 2.29 s
[10:51:16] Starting 'server:start'...
[10:51:16] Finished 'server:start' after 19 ms
[10:51:16] Starting 'default'...
[10:51:17] Finished 'default' after 942 ms
[10:51:17] Development server listening. (PID:52108)
You can now see your skeleton app by navigating to http://localhost:3000

Optional - Downloading WebStorm

As this part is optional I won't explain the steps. Downloading and running software should be simple enough.

  1. Download webstorm to your Downloads folder
  2. cd ~/Downloads
  3. tar -xvcf WebStorm[the rest of the download's file name]
  4. sudo mv ./WebStorm-....... /opt/webstorm
  5. /opt/webstorm/bin/webstorm.sh 

Using strongly typed libraries

One of the benefits of using TypeScript instead of JavaScript is that it is strongly typed. The problem is that not all libraries are written in TypeScript and so no type information is provided by default. Thankfully the Definitely Typed project has a huge collection of definition files to help us. They are implemented as a set of interfaces; TypeScript interfaces don't transpile to JavaScript, they are just for type safety checking.

By default the Yo express-typescript template adds some of these files, but it uses an out-of-date method of referencing them. So, the next thing we will do is to remove the strongly typed definition files for this project in order to add the newer ones using the new recommended approach.

  1. Delete the tsd.json file
  2. Delete the typings folder (type rm -r typings)
  3. Edit the /app.ts and /routes/index.ts files
Next we need to install the Node package "typings", we'll do this globally so that it is available as a standard npm command and can be used when creating new projects.
sudo npm install -g typings
Now make sure you are in the root of your project and initialise typings for it.
typings init 
Now instead of downloading Definitely Typed files and putting them somewhere in our project's sources we can search for libraries using the typings search command, and install them using the typings install command.

If you look in one of your project's TS files using an editor like WebStorm you will see that the require keyword is no longer recognised. This is because the typings folder we deleted contained library definitions to give us strongly typed information for Node itself. The first thing we want to do is to correct that. Type in the command

typings search node
This will show you all strongly typed definition files that match the search phrase. An important thing to pay attention to is the Source column. When installing one of these libraries we need to prefix its name with the value in that column, separated with a tilde. For example, the "node" entry has a source "dt", so it's fully qualified name for installing is "dt~node".

Whenever we are using the Definitely Typed libraries (source = dt) we need to add the --global option to our command line so typings knows to resort to those legacy files. We also want to include the --save option so that our typings.json file is updated.

typings install --global --save dt~node
Now if you look back at one of your TS files you should see the require keyword is now recognised!

By looking through dependencies section of the package.json file we can see which libraries Yo has specified are needed for this skeleton site, and install the typings for each of them. To save you some time, just copy/paste the following lines into your terminal window.

typings install --global --save dt~express (fixes Request/Response)
typings install --global --save dt~express-serve-static-core
typings install --global --save dt~serve-static
typings install --global --save dt~mimetypings install --global --save dt~q
typings install --global --save dt~underscore
typings install --global --save dt~mongoose
typings install --global --save dt~serve-favicon
typings install --global --save dt~morgan
typings install --global --save dt~cookie-parser
typings install --global --save dt~body-parser
Now, some of the packages in the package.json are listed under the devDependencies section. When installing these we need to use --save-dev rather than just --save, this will ensure they go into the development environment's requirement list rather than the runtime list.
typings install --global --save-dev dt~should
typings install --global --save-dev dt~superagent
typings install --global --save-dev env~mocha
typings install --save-dev debug
Note that the final dependency "debug" does not have the --global option specified. This is because when you do a typings search for "debug" you will see there are two entries. Once has a source of npm, and the other has a source of dt (Definitely Typed project). The npm one is newer so we want to use that one, and since npm is the default source we don't need the prefix~ before the name, and we don't need to specify --global to tell typings it needs to look through the Definitely Typed repository.

2016-02-25

[Solved] MVCApplication Parser Error

I have this problem because I have to develop with VS running as administrator in order to use Local IIS hosting. When files are added (or generated by compiling) I think VS was setting the owner as Administrator, which IIS then cannot read. Start a command prompt as Administrator and type in the following
icacls c:\devpath\yourwebsite /grant everyone:(OI)(CI)F /T

2016-02-24

How to Create a Self Signed Certificate in IIS 7

I know I am going to want this link again in the future!

2016-02-04

VSTO Office plugin not appearing in Office 2007

If you have an office VSTO plugin that is working in other versions of Office but not appearing in Office 2007 then try setting the following registry value

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\Common\General\

Name = EnableLocalMachineVSTO
Value (DWORD) = 1


2016-02-01

Watching a single property in an array in AngularJS

A typescript example that converts the array's property into a single string that can be watched.
        $scope.$watch(
            () => this.someArray.map(x => x.selected ? "1" : "0").join(""),
            (newValue, oldValue, scope) => this.onSelectionChanged(this.getSelectedItems()));

Getting an AngularJS directive to call back a method on its parent's controller

Here is a TypeScript example of how to call back a method on the controller from an embedded directive. The most important thing to note is that the directive's parameter name for your callback uses a & when defined, and when calling that callback you should not use positional parameters but instead use an object with properties having the names of the parameters in the target.
Register the directive when you create your app module:
module MyApp {
    var app: angular.IModule = angular.module("MyApp");
    MyApp.Directives.FileUploader.register(app);
}
The registration code is as follows:
module MyApp.Directives.FileUploader {
  class FileUploaderDirective implements angular.IDirective {
      public restrict: string = "E";
      public templateUrl: string = "/app/Directives/FileUploader/FileUploaderDirective.html";

      //IMPORTANT - Use & to identify this as a method reference
      public scope: any = {
        onFileItemClicked: "&"
      };
      public controller: string = "MyApp.Directives.FileUploader.Controller";
      public controllerAs: string = "controller";
      public bindToController: boolean = true;
      public transclude: boolean = true;
      public replace: boolean = true;
  }

  export function register(app: angular.IModule) {
      app.controller("MyApp.Directives.FileUploader.Controller", Controller);
      app.directive("fileUploader", () => new FileUploaderDirective());
  }
}
The directive's controller would look like this
module MyApp.Directives.FileUploader {
    export class Controller {
        public files: string[] = ["One", "Two", "Three"];
        //The callback specified in the view that created this directive instance
        public onFileItemClicked: (fileItem) => void;

        // This is the controller method called from its HTML's ng-click
        public fileItemClicked(fileItem) {
            //IMPORTANT: Don't use comma separated parameters,
            //instead use an object with property names to act as named parameters
            this.onFileItemClicked({
                fileItem: fileItem
            });
        }
    }
}
The directive's HTML would look something like this
  • ng-repeat="item in controller.files" ng-click="controller.onFileItemSelected(item)"> {{ item }}
  • The main view will have an instance of your directive like so
     ng-app="MyApp" ng-controller="MainController as controller">
       on-file-item-clicked="controller.fileItemClicked(fileItem)"/>
    
    Now all you need on your MainController is a method
    public fileItemClicked(fileItem) {
      alert("Clicked " + fileItem);
    }

    2016-01-29

    AngularJs - binding HTML

    The directive ng-bind will escape HTML to avoid data acting maliciously. If you want to output html you need to use ng-bind-html

    <div ng-bind-html="someHtmlBody"/>

    The important step to getting this working is to ensure the script angular-sanitize.js is referenced on your page, and it is specified as a dependency when creating a module....

    var app = angular.module("MyApp", ["ngSanitize", "OtherDependencies"]);