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
npm start
which callswebpack-dev-server --inline --progress --port 8085
npm run backend
which callsnodemon --watch server ./server/bin/www
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.