In this post, we detail the setup of a minimal TypeScript project template that we can build upon for other project templates (e.g., Express, GraphQL, MongoDB). Despite being minimal, it sports hot reloading, linting, and code formatting. This has been cobbled together from a variety of resources (see below).

Initializing the Project

Create a directory for the template and initialize a Git repo and a new Node project:

mkdir typescript-node-template
git init
npm init -y

Create a .gitignore with the following:

dist/
node_modules/

Install some development dependencies:

npm i -D typescript tsc-watch
  • typescript compiler
  • tsc-watch watches for TypeScript file changes and efficiently recompiles them

Install the ambient types for Node:

npm i -D @types/node

Configuration

Create a default TypeScript config file:

npx tsc --init

Change the following compilerOptions values from their defaults:

  • sourceMap: false - no separate source map, since we’re using inline maps (default)
  • baseUrl: "." - default value, but required to be explicitly set when paths is set
  • paths: { "*": ["./src/*"] } - search for imports within the src directory
  • outDir: "dist" - put all the built files here
  • typeRoots: ["node_modules/@types"] - where to explicitly look for type definitions

Add the following entries after the compilerOptions:

  "include": ["node_modules/@types", "src/**/*"],
  "exclude": ["node_modules", "dist"]
  • include - tells the compiler what to include
  • exclude - tells the compiler what to exclude

Code Linting and Formatting

Install the development dependencies for linting:

npm i -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Create an .eslintrc.js file in the project root and add the following:

module.exports = {
  parser: "@typescript-eslint/parser", // Specifies the ESLint parser
  extends: [
    "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
  ],
  rules: {
    // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
    // e.g. "@typescript-eslint/explicit-function-return-type": "off",
  },
};

Install the development dependencies for formatting:

npm i -D prettier eslint-config-prettier eslint-plugin-prettier

Create a .prettierrc.js and add the following:

module.exports = {
  semi: true,
  trailingComma: "all",
  singleQuote: true,
  printWidth: 120,
  tabWidth: 2,
};

Along with a .prettierignore:

node_modules
dist
package-lock.json
*.lock
.gitignore
.prettierignore

Sample Code

Create a src directory and an index.ts file in it with the following code:

class Dog {
  name: string;
  constructor(data: string) {
    this.name = data;
  }
}

const dog = new Dog("Rover");
if (dog instanceof Dog) {
  console.log(`${dog.name} is a dog`);
}

To test, run following command from the terminal:

npm run dev

Hot Reloading

We are using tsc-watch to provide file watching and incremental recompilation to JavaScript (in dist/), which we can run the standard node executable on.

Add the following NPM command to the scripts section of package.json:

  "dev": "tsc-watch --noClear --onSuccess 'node dist/index.js'"
  • --noClear - prevents the console from clearing between recompilations
  • --onSuccess - if the compilation was successful, then run the resulting JavaScript with Node.js

To test, run following command from the terminal:

npm run dev

and you should see the output: Rover is a dog.

Try changing the console output and saving. You should see the output in the terminal indicate that it detected a file change and, if there are no errors, your updated ouput.

Building

Add another NPM command to the scripts of package.json:

  "build": "tsc -b",

To build the project, simply:

npm run build

To test:

node dist/index.js

Final Steps

Create a README.md, add a LICENSE, and commit/push! Now we have foundational template we can build on, tweaking and extending it as-needed.

Resources