Guilherme Nicolini

My setup Node.js + Typescript for backend

August 15, 2023

ESLint
Jest
NodeJS
Typescript
VSCode

Learn how to configure a complete environment to develop your NodeJS backend projects.

Get Started

Setup a good development environment for backend with Node.js is not a trivial thing like many posts shows. In this article, I’ll do a step-by-step of how and why to use the settings that I use. But remember, this is my opinion based on my own experiences.

Prerequisites

NVM

Node Version Manager (NVM) is essential for managing different Node.js versions for each project. This allows you to use a different version for each open terminal simultaneously. It is available for all operating systems.

Git

Git is essential for managing the source code of your projects. It is available for all operating systems.

Visual Studio Code

Visual Studio Code will be the code editor we use. It is available for all operating systems.

Setup Visual Studio Code

Profile

To start, we will create a development profile for our setup:

  Manage > Profiles > Create Profile
  Profile name: Backend (Node.js)
  [x] Settings
  [x] Keyboard Shortcuts
  [x] User Snippets
  [x] User Tasks
  [x] Extensions
  Create

Font

Fira Code: A monospaced font with ligatures. Simply follow the link, download, and install it on your operating system.

Access the configuration file at File > Preferences > Settings > Open Settings (JSON) and add the following lines:

  "editor.fontFamily": "Fira Code",
  "editor.fontLigatures": true,
  "editor.fontWeight": 500,
  "editor.fontSize": 16,
  "editor.tabSize": 2

Formatting

Access the configuration file at File > Preferences > Settings > Open Settings (JSON) and add the following lines:

  "editor.guides.bracketPairs": true,
  "editor.renderWhitespace": "boundary",
  "editor.codeActionsOnSave": { "source.fixAll.eslint": true }

Themes

Access extensions and add:

  • One Dark Pro (binaryfy)
  • Material Icon Theme (Philipp Kief)

Access the configuration file at File > Preferences > Settings > Open Settings (JSON) and add the following line:

  "workbench.colorTheme": "One Dark Pro Darker",
  "workbench.iconTheme": "material-icon-theme"

Other configurations

  "typescript.tsserver.experimental.enableProjectDiagnostics": true,
  "diffEditor.ignoreTrimWhitespace": false

Snippets

Create a new snippet at:

  Manage > User Snippets > New Global Snippets file... New Snippets
  Snippet name: Jest Snippets
  Enter

Add the following code:

{
  "Jest Describe Template": {
    "scope": "javascript, typescript",
    "prefix": "describe",
    "body": [
      "describe('$1', () => {",
      "  test('$2', () => {",
      "  })",
      "})",
      ""
    ]
  },
  "Jest Describe Async Template": {
    "scope": "javascript, typescript",
    "prefix": "describe-async",
    "body": [
      "describe('$1', () => {",
      "  test('$2', async () => {",
      "  })",
      "})",
      ""
    ]
  },
  "Jest Test Template": {
    "scope": "javascript, typescript",
    "prefix": "test",
    "body": [
      "test('$2', () => {",
      "})",
      ""
    ]
  },
  "Jest Test Async Template": {
    "scope": "javascript, typescript",
    "prefix": "test-async",
    "body": [
      "test('$2', async () => {",
      "})",
      ""
    ]
  }
}

Extensions

Access extensions and add:

  • Draw.io Integration (Henning Dieterichs)
  • EditorConfig for VS Code (EditorConfig)
  • ESLint (Microsoft)

Project

EditorConfig

Create a .editorconfig file in the project’s root with the following content:

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

This file standardizes code style across various editors and IDEs.

NVMRC

Create a .nvmrc file in the project’s root with the content:

  18.17.1

This file specifies the correct Node.js version for the project to work. At the time of writing this article, this was the Node.js version.

Start project

  npm init -y

Setup Husky

Install Husky using the recommended command found here:

  npx husky-init && npm install

Create a .gitignore file in the project’s root with the content:

  node_modules

Change the test script in the package.json file to:

  "test": ""

Install the git commit linter to standardize commit messages:

  npm i -D git-commit-msg-linter

Create a hook for commit-msg:

  npx husky add .husky/commit-msg '.git/hooks/commit-msg $1'

Setup Typescript

  npm i -D typescript ts-node-dev @types/node dotenv

Create a tsconfig.json file in the project’s root with the content:

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDirs": [
      "src",
      "tests"
    ],
    "strict": true,
    "target": "ES2021",
    "module": "CommonJS",
    "allowJs": true,
    "removeComments": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strictPropertyInitialization": false,
    "sourceMap": true,
    "baseUrl": "src",
    "paths": {
      "@/tests/*": [
        "../tests/*"
      ],
      "@/*": [
        "*"
      ]
    }
  },
  "include": [
    "src",
    "tests"
  ]
}

This file configures Typescript with some important options:

  • Sets the compilation output directory to ‘dist’
  • Sets ES2021 as the default ECMAScript target
  • Defines two base folders:
    • src: source files
    • tests: test files
  • Defines 2 path aliases:
    • @: points to the src/ directory
    • @/tests: points to the tests/ directory

Setup ESLint with JavaScript Standard Style

I prefer the Standard style guide, but feel free to use another one or none at all. Just remember that when you have a development team, it’s good to use some style guide for code standardization.

  npm i -D eslint @typescript-eslint/eslint-plugin eslint-config-standard-with-typescript

Create a .eslintignore file in the project’s root with the content:

  node_modules

Create a .eslintrc.json file in the project’s root with the content:

{
  "extends": "standard-with-typescript",
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "rules": { }
}

This file configures ESLint to work with Typescript.

Setup Jest

  npm i -D jest jest-mock-extended ts-jest @types/jest

Create a jest.config.js file in the project’s root with the content:

module.exports = {
  collectCoverageFrom: [
    '<rootDir>/src/**/*.ts',
    '!<rootDir>/src/**/index.ts',
    '!**/*.d.ts'
  ],
  coverageDirectory: 'coverage',
  testEnvironment: 'node',
  reporters: ['default'],
  coverageReporters: ['lcov', 'clover', 'text-summary', 'text', 'cobertura'],
  moduleNameMapper: {
    '@/tests/(.*)': '<rootDir>/tests/$1',
    '@/(.*)': '<rootDir>/src/$1'
  },
  transform: {
    '\\.ts$': 'ts-jest'
  },
  roots: [
    '<rootDir>/src',
    '<rootDir>/tests'
  ],
  clearMocks: true
}

This file configures Jest with some important options:

  • Ignores coverage for index.ts files (typically used to export modules)
  • Ignores coverage for d.ts files (type declaration files)
  • Maps the root @ to the respective folders

Create a jest.unit.config.js file in the project’s root with the content:

module.exports = {
  ...require('./jest.config.js'),
  testMatch: ['**/*.spec.ts']
}

This file only tests spec.ts files.

Create a jest.integration.config.js file in the project’s root with the content:

module.exports = {
  ...require('./jest.config.js'),
  testMatch: ['**/*.test.ts']
}

This file only tests test.ts files.

Add the following scripts to the package.json file:

  "test": "jest --passWithNoTests --no-cache --runInBand",
  "test:unit": "npm t -- --watch --config ./jest.unit.config.js",
  "test:integration": "npm t -- --watch --config ./jest.integration.config.js",
  "test:coverage": "npm t -- --coverage"

Add the coverage folder to the .gitignore and .eslintignore files:

  node_modules
  coverage

Create a hook for pre-push:

  npx husky add .husky/pre-push 'npm run test:coverage'

Setup Lint Staged

  npm i -D lint-staged

Add the following script to the package.json file:

  "test:staged": "npm t -- --findRelatedTests"

Create a .lintstagedrc.json file in the project’s root with the content:

{
  "*.ts": [
    "eslint 'src/** --fix",
    "npm run test:staged"
  ]
}

Alter the .husky/pre-commit file to:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged

This file formats and runs tests related to staged files before committing.

Setup Updates

  npm i -D npm-check-updates

Add the following script to the package.json file:

  "update": "ncu --color --interactive && npm i"

This script checks for packages to update.

Debugging

Create a .vscode/launch.json file with the content:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Jest Tests",
      "runtimeArgs": [
        "--inspect-brk",
        "${workspaceRoot}/node_modules/.bin/jest",
        "--runInBand"
      ],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Dev",
      "cwd": "${workspaceFolder}",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run-script", "dev"]
    }
  ]
}

This creates two tasks. Simply place a breakpoint on the desired line and execute:

  • Debug Jest Tests: Debugging in a test environment
  • Debug Dev: Debugging in a development environment

Build

  npm i module-alias
  npm i -D @types/module-alias rimraf

Create a tsconfig-build.json file with the content:

{
  "extends": "./tsconfig.json",
  "exclude": [
    "tests"
  ]
}

This file ignores all files in the tests folder during compilation.

Add the dist folder to the .gitignore and .eslintignore files:

  node_modules
  coverage
  dist

Add the following script to the package.json file:

  "build": "rimraf dist && tsc -p tsconfig-build.json"

Hello World Application

Create the following files inside the src folder of your project:

// index.ts
import './alias'
import { hello } from '@/start'

hello()
// alias.ts
import { addAlias } from 'module-alias'
import { resolve } from 'path'

addAlias('@', resolve(process.env.TS_NODE_DEV === 'true' ? 'src' : 'dist'))
export const hello = (): void => {
  console.log('Hello World!')
}

Add the following scripts to the package.json file:

  "dev": "ts-node-dev --respawn --transpile-only --ignore-watch node_modules --clear -r dotenv/config src/index.ts",
  "start": "node dist/index.js"

To run the application in development mode, execute:

  npm run dev

To run the application in production mode, execute:

  npm run build && npm start

Done! Now you have your development environment configured

You can find all the source code for this article at https://github.com/guilhermenicolini/setup-node-typescript