Modern Node.js TypeScript 101


2017-04-13 · 4 min read

TypeScript is a typed superset of JavaScript that compiles to idiomatic JavaScript. Let's break this definition down. « superset » means it adds a few features on top of JavaScript while staying aligned with ECMAScript; « typed » means it provides strong typing in the form of optional static types and compile-time checks; finally, it compiles to plain, readable JavaScript. These features allow to enforce contracts during the development phase and help find more bugs before runtime phase. The project was initiated by Microsoft and is under Apache 2 license.

Why TypeScript? JavaScript is one of the most prominent programming languages today with large community actively working both on improving the language and its ecosystem. There are, however, still many flaws in the language design, in the end JavaScript has been developed in only 10 days. JavaScript is a dynamic programming language which allows to do things like extending objects and definitions at runtime and changing types at will. This flexibility is also a source of errors, so potentially avoidable bugs often slip into production to appear only at runtime. TypeScript was created to help resolve some of those problems.

TypeScript supports the same types as in JavaScript with an additional enumaration type.

let isDone: boolean = false;
let age: number = 16;
let name: string = 'Zaiste';
let intro: string = `My name is ${name} and I'm ${age} years old`;

Arrays can be declared as the type followed by [] or using generics.

let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];

Enum is used to give more friendly names to sets of numeric values.

enum Color { Red, Green, Blue }
let c: Color = Color.Green;

any describes the type of variables with unknown content e.g. dynamic data coming from users or 3rd party library. It is very convenient to use any to gradually introduce type-checking.

let inputField: any = 7;

void denotes the absence of any type, it is usually used as the return type of functions that do not return a value. It doesn't make sense to use it for declaring variables.

function print(message): void {
  console.log("--", message);
}

Finally, never represents the type of values that never occur, e.g. a function that always throws an exception can use never to describe its return type.

function error(message: string): never {
  throw new Error(message);
}

There are also null and undefined types that are subtypes of all other types and usually aren't useful on their own. With the --strictNullChecks compiler flag enabled, null and undefined can only be assigned to void and their respective types. TypeScript authors encourage to have this flag enabled by default to avoid many common errors.

Type assertations are used to make variable's type more specific that its current type. It's like a type cast, but without the compiler checking it.

let title: any = "this is a string";
let titleCharartersCount: number = (title as string).length;

TypeScript is much more than such simple types. Check TypeScript Handbook for a comprehensive introduction to the language.

Let's now setup a simple Node.js project within an empty modern-nodejs-typescript directory using TypeScript.

yarn init -y

Let's install TypeScript compiler

npm install typescript -g

For convenience, TypeScript provides types adnotations to regular JavaScript packages so their types are available in TypeScript projects. They are installed with npm and usually have the same name as the package itself, but prefixed with @types/, e.g. here's how to install type declarations for Node

yarn add @types/node --dev

Let's generate tsconfig.json, a TypeScript configuration file which is used to manage your project’s options. Here you can check all the TypeScript configuration options.

tsc --init

Here's an example of tsconfig.json. It specifies es2017 as the target, it uses commonjs syntax for importing modules and it outputs plain JavaScript files to build directory.

{
  "compilerOptions": {
    "outDir": "build",
    "module": "commonjs",
    "target": "es2017",
    "noImplicitAny": false,
    "sourceMap": false
  }
}

Let's create a simple TypeScript application that consists of app.ts & lib/index.ts. Here's app.ts

import add from './lib';

async function main() {
  const result = await add(7, 9);
  console.log(result);
}

main();

And here's lib/index.ts

async function add(a:number, b:number) {
  return Promise.resolve(a + b);
}

export default add;

TypeScript workflow involves transpilation, which means that TypeScript files must be translated into JavaScript (build phase) before being able to run it. To simplify the process let's define helpers commands in scripts section of package.json

"scripts": {
  "build": "tsc -p ."
}

You can also run TypeScript code directly without having to compile it with ts-node, a TypeScript execution environment and REPL for Node.js.

npm install ts-node -g
ts-node app.ts

PM2, one of the most popular Node.js process managers, can run TypeScript directly.

pm2 install typescript
pm2 run index.ts