Start a TypeScript Node Project
Minimal selection of tools and libraries for a TypeScript Node project.
Prerequisites
This setup has been tested with node v18.16.0, npm 9.7.1, eslint 8.45.0, typescript-eslint 6.0.0, prettier 3.0.0. Basic knowledge of npm is assumed. Reading reference documentation for corresponding tools is recommended.
Some options are not standard and disabled by default. I consider those options to be useful and enable them in my projects.
Create a new project
We will use a minimal package.json
with ESM modules.
% mkdir my-project
% cd my-project
package.json
:
{
"private": true,
"type": "module"
}
Check JSON syntax:
% npm install
Add and configure TypeScript
@tsconfig/node18
is a tiny
package which provides some default values for tsconfig to extend from. It’s
recommended by typescript
documentation. Alternatively we can just copy its contents into tsconfig.json
to avoid extra dependency.
@types/node
provides type
definitions for Node.js API.
We added the exactOptionalPropertyTypes
option to make TypeScript more strict
about optional properties. It is a recommended option that is not enabled by
default.
% npm install --save-dev 'typescript' '@tsconfig/node18' '@types/node'
tsconfig.json
:
{
"extends": "@tsconfig/node18/tsconfig.json",
"include": ["src"],
"compilerOptions": {
"exactOptionalPropertyTypes": true,
"rootDir": "src",
"outDir": "dist",
"verbatimModuleSyntax": true
}
}
Now we can add two files in order to test our setup.
Please note that we’re using .js
file extension in import
statement in the
.ts
file. This is not a typo or error. That’s how TypeScript works with ESM.
src/concat.ts
:
export function concat(a: string, b: string): string {
return a + " " + b;
}
src/index.ts
:
import { concat } from "./concat.js";
console.log(concat("Hello", "world!"));
Now we will compile, inspect results and run the code. Notice that JavaScript output is pretty much identical to the TypeScript minus types.
% npx tsc
dist/concat.js
:
export function concat(a, b) {
return a + " " + b;
}
dist/index.js
:
import { concat } from "./concat.js";
console.log(concat("Hello", "world!"));
% node dist/index.js
Hello world!
Add and configure ESLint
The default ESLint configuration is a good starting point. However, there are
plenty of additional checks that are
not enabled by default. For instance, the
eqeqeq
check has been added.
% npm install --save-dev 'eslint' '@typescript-eslint/eslint-plugin' '@typescript-eslint/parser'
.eslintignore
:
/dist/
.eslintrc.json
:
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/strict-type-checked",
"plugin:@typescript-eslint/stylistic-type-checked"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"project": "tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"root": true,
"rules": {
"curly": ["error", "multi-line"],
"eqeqeq": "error",
"@typescript-eslint/strict-boolean-expressions": [
"error",
{
"allowString": false,
"allowNumber": false,
"allowNullableObject": false
}
]
}
}
Check that it works:
% npx eslint .
Add and configure Prettier
Prettier provides good defaults out of the box and even maintains a
philosophy regarding its
options. In this guide, we will change the trailingComma
option.
% npm install --save-dev 'prettier'
.prettierignore
:
/dist/
.prettierrc.json
:
{
}
Now we can check if our sources are properly formatted:
% npx prettier -c .
Checking formatting...
All matched files use Prettier code style!
Unit testing
Node.js has a built-in test runner. We will add a unit test for our concat
function.
src/concat.test.ts
:
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { concat } from "./concat.js";
await test("concat works", () => {
assert.equal(concat("a", "b"), "a b");
});
Now we can compile and run this unit test. node --test dist
will find all
JavaScript files with names matching
some rules
inside dist
directory and will execute every one of them.
% npx tsc
% node --test dist
✔ concat works (0.48175ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 45.819834
Configure npm scripts
It’s a good idea to add a few npm scripts so we won’t have to memorize all the commands and their options.
package.json
:
{
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"check": "eslint --max-warnings 0 . && prettier --check .",
"start": "node dist/index.js",
"start:watch": "node --watch dist/index.js",
"test": "node --test dist",
"test:watch": "node --test --watch dist"
},
...
}
Now we can execute npm run build:watch
in the background terminal and our
TypeScript code will be constantly compiled as it changes. npm start
will
start our application and npm run start:watch
will restart it on any code
change. Similarly we can use npm test
to run tests once or
npm run test:watch
to run tests on every code change.
IDE remarks
I’m using VSCode with the following extensions: ESLint, Prettier. It supports TypeScript out of the box. Here’re some settings which configure Prettier to be a default formatter and to run it on save:
{
...,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"prettier.proseWrap": "always",
...
}
It’s possible to configure WebStorm or Idea with ESLint and Prettier as well.
Using --watch
options allows for very fast feedback loops.