100 days of Angular 2 - Day 9: Converting the backend to TypeScript

Convert server side code from JavaScript to TypeScript

I'd like to bring Angular Universal into the project soon (before the application gets much bigger), but all of the Angular Universal code, documentation, and starter templates are written in TypeScript. Currently, my server side code consists of three files (app.js, bin/www, and routes/api.js), all written in JavaScript.

For consistency I'll first convert my app.js to app.ts before trying to bring in Angular Universal, since it's confusing to look at documentation and reference implementations in another language. I'll start with app.js and tackle routes/api.js later.

Once I renamed my app.js to app.ts I received some errors.

I fixed this by just saying err is of type any like so

var err : any = new Error('Not Found');

and that error went away.

Visual Studio Code then didn't show any more issues, but as soon as I tried to compile the app I received the following errors:

$ tsc server/app.ts
server/app.ts(1,15): error TS2304: Cannot find name 'require'.
server/app.ts(2,24): error TS2304: Cannot find name 'require'.
server/app.ts(3,12): error TS2304: Cannot find name 'require'.
server/app.ts(4,15): error TS2304: Cannot find name 'require'.
server/app.ts(5,14): error TS2304: Cannot find name 'require'.
server/app.ts(6,20): error TS2304: Cannot find name 'require'.
server/app.ts(7,18): error TS2304: Cannot find name 'require'.
server/app.ts(9,11): error TS2304: Cannot find name 'require'.
server/app.ts(20,34): error TS2304: Cannot find name '__dirname'.
server/app.ts(21,34): error TS2304: Cannot find name '__dirname'.
server/app.ts(57,1): error TS2304: Cannot find name 'module'.

The first few errors are due to lines like this

var express = require('express');

Which should be replaced as something like this

import * as express from 'express';

Then I received different errors:

Install the declaration files

With TypeScript 2.x I needed to install the declaration files like below. Note that TypeScript 1.x does not use the npm / @types method of installing files and you'll need to use the typings or tsd tools.

$ npm install @types/express --save-dev
$ npm install @types/cookie-parser --save-dev
$ npm install @types/body-parser --save-dev
$ npm install @types/morgan --save-dev
$ npm install @types/serve-favicon --save-dev

I received a 404 (not found) when trying to install the declaration files for express-sanitizer. I have three options:

Option 1 - Write a declaration file for this module. The TypeScript wiki has a page on writing declaration files. It doesn't look very easy, and I'm not very familiar with the express-santizer module, seeing as I'm not the original author of it.

Option 2 - Declare the module as type :any and move on. I want a way to tell the TS compiler that I know there is no type definition for this and I accept that expressSantizer will be :any. In my app.ts it would look like this

declare var expressSanitizer: any;
var expressSanitizer = require('express-sanitizer');

Option 3 - Use a different third party module that has TypeScript definition files. I only needed this module to sanitize user input on one page, and there looks to be a more popular (and probably better supported) package called sanitizer that already has a TypeScript definition file -- I used the TypeScript Types Search Engine to verify that one exists.

I'll go with option 3.

$ npm uninstall --save express-sanitizer
$ npm install --save sanitizer

After tweaking the code where I use the sanitizer (routes/api.js) and removing it from app.ts I no longer get any errors about missing modules when trying to compile the file.

Adding the types

Next a went through the existing code and added some types. For example,

var app = express();

became

var app: express.Application = express();

And

app.use(function(err, req, res, next) { ... }

became

app.use(function(err: any, req: express.Request, res: express.Response, next: express.NextFunction) { ... }

Compilation is now a necessary step

Up until now I would run the back end (server) with the following npm script during development, as defined in my package.json

"scripts": {
    "backend": "nodemon --watch server ./server/bin/www",
}

nodemon is used to watch the server/ directory and restart the server when it detects any changes to the files. This allows me to change the server logic and see the results quickly. But now I need to compile the server before restarting it!

I can't run tsc --watch because it will go and compile all the TypeScript files, including those on the front end (the Angular 2) code, which isn't necessary as they're handled with the webpack-dev-server.

What would be really nice is to specify a directory for tsc to --watch, in my case, the server/ directory. Then I could run tsc --watch concurrently with nodemon --watch which would allow me to write my server in TypeScript and get auto-restart on every file change.

As it turns out, this can be achieved like so:

$ tsc server/*.ts --watch
2:28:58 PM - Compilation complete. Watching for file changes.

However, if I try to turn this into an npm script

"scripts": {
    "backend-compile": "tsc server/*.ts --watch",
},

and run it, it doesn't quite work

$ npm run backend-compile

> angular-quickstart@1.0.0 backend-compile C:\Users\chq-stephene\git\100-days-of-angular
> tsc server/*.ts --watch

error TS6053: File 'server/*.ts' not found.
2:29:20 PM - Compilation complete. Watching for file changes.

Instead of tracking this error down and searching for a solution, I'm going to take a break and be done for the day.

In summary, here's what I'll now run during development:

3 terminals open

  1. npm start which calls webpack-dev-server --inline --progress --port 8085
  2. npm run backend which calls nodemon --watch server ./server/bin/www
  3. tsc server/*.ts --watch

And here's what I'll need to run during a production deployment:

git pull
npm install
npm run build
export NODE_ENV=production
tsc server/*.ts
forever start ./server/bin/www

When I went to perform the build, I got all sorts of errors.

stephen@ubuntu:~$ cd apps/100-days-of-angular2/
stephen@ubuntu:~/apps/100-days-of-angular2$ npm run build
> angular-quickstart@1.0.0 build /home/stephen/apps/100-days-of-angular2
> rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail
4660ms build modules      
11ms seal
51ms optimize
15ms hashing
41ms create chunk assets
10303ms additional chunk assets
3853ms optimize chunk assets
90% optimize assetsError in bail mode: [default] /home/stephen/apps/100-days-of-angular2/node_modules/@types/express/node_modules/@types/express-serve-static-core/node_modules/@types/node/index.d.ts:102:5 
Duplicate identifier 'BufferEncoding'.
npm ERR! Linux 4.2.0-42-generic
npm ERR! argv "/usr/bin/nodejs" "/usr/bin/npm" "run" "build"
npm ERR! node v4.5.0
npm ERR! npm  v2.15.9
npm ERR! code ELIFECYCLE
npm ERR! angular-quickstart@1.0.0 build: `rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail`
npm ERR! Exit status 1

This is a little puzzling as the webpack build should only be building the Angular 2 (front end) files, and the error is complaining about an express module (part of the back end), which doesn't have anything to do with the front end code.

I'm not entirely sure what's going on here.

Revert! Undo!

I attempted to revert to a previously working state on the production server by cloning the repository, checking out a previous revision from day 8, and performing a build.

stephen@ubuntu:~/apps$ git clone https://github.com/netinstructions/100-days-of-angular2 100-days-of-angular2-take-2
Cloning into '100-days-of-angular2-take-2'...
remote: Counting objects: 193, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 193 (delta 4), reused 0 (delta 0), pack-reused 179
Receiving objects: 100% (193/193), 150.85 KiB | 0 bytes/s, done.
Resolving deltas: 100% (95/95), done.
Checking connectivity... done.
stephen@ubuntu:~/apps$ cd 100-days-of-angular2-take-2/
stephen@ubuntu:~/apps/100-days-of-angular2-take-2$ git checkout 584796df99ad583910bf78d6c6635ba7f0b9fbde
Note: checking out '584796df99ad583910bf78d6c6635ba7f0b9fbde'.
You are in 'detached HEAD' state.
git checkout -b new_branch_name
HEAD is now at 584796d... day 8 - show validation and be responsive
stephen@ubuntu:~/apps/100-days-of-angular2-take-2$ npm install
stephen@ubuntu:~/apps/100-days-of-angular2-take-2$ ... done
stephen@ubuntu:~/apps/100-days-of-angular2-take-2$ npm run build
> angular-quickstart@1.0.0 build /home/stephen/apps/100-days-of-angular2-take-2
> rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail
4334ms build modules      
10ms seal
33ms optimize
17ms hashing
50ms create chunk assets
11136ms additional chunk assets
4605ms optimize chunk assets
90% optimize assetsError in bail mode: [default] /home/stephen/apps/100-days-of-angular2-take-2/client/app.component.ts:5:14 
Cannot find name 'require'.

I have no idea what's going on. This was working yesterday.

If I try the same actions on my local machine (git clone, git checkout day 8 commit, npm install, npm run build) I get the following:

$ npm run build

> angular-quickstart@1.0.0 build C:\Users\stephen\100-days-of-angular2-take-2
> rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail
813602ms o 90% optimize assets Error in bail mode: [default] C:\Users\stephen\100-days-of-angular2-take-2\client\day-001-vegetables\vegetable.service.ts:30:22
Cannot find name 'Promise'.

I think the world is conspiring against me. Both of my environments (dev / production) are refusing to perform a build that worked on day 8 with the same code.

What changed? What's going on?

Go to day 10 here.