I will walk you through setting up basic Express server with TypeScript. By “basic” I mean:

eslint + prettier config

testing setup (mocha, chai, supertest)

alias for local dependencies, so that you can import modules from root

watch mode for faster development

TypeScript setup

VSCode workspace settings

I assume you have node and typescript globally installed.

Let’s create our directory:

mkdir express-boilerplate && cd express-boilerplate

Set up git:

git init

Set up npm:

npm init -y

Add ./.gitignore file:

logs

*.log

npm-debug.log*

pids

*.pid

*.seed

*.pid.lock

node_modules

*.tsbuildinfo

.env

build

Add all dependencies we will need:

Dependencies:

body-parser

express

npm i --save body-parser express

Dev dependencies:

Add VSCode settings in ./.vscode/settings.json file:

{

"editor.tabSize": 2,

"editor.detectIndentation": false,

"editor.renderWhitespace": "boundary",

"files.exclude": {

"build": true,

},

"eslint.autoFixOnSave": true,

"eslint.validate": [

"javascript",

"javascriptreact",

{ "language": "typescript", "autoFix": true },

{ "language": "typescriptreact", "autoFix": true }

]

}

Make sure you have these extensions in your VSCode installed:

ESLint (dbaeumer.vscode-eslint)

Prettier — Code formatter (esbenp.prettier-vscode)

Add ./.eslintrc.js config file:

module.exports = {

parser: '@typescript-eslint/parser',

extends: [

'plugin:@typescript-eslint/recommended',

'prettier/@typescript-eslint',

'plugin:prettier/recommended',

],

parserOptions: {

ecmaVersion: 2018,

sourceType: 'module',

},

rules: {

"@typescript-eslint/explicit-function-return-type": 0

},

};

Add ./.prettierrc.js config file (use settings you prefer):

module.exports = {

semi: true,

trailingComma: 'all',

singleQuote: true,

printWidth: 120,

tabWidth: 2,

};

Add ./tsconfig.json file:

{

"compilerOptions": {

"module": "commonjs",

"esModuleInterop": true,

"target": "es6",

"noImplicitAny": true,

"moduleResolution": "node",

"sourceMap": true,

"outDir": "build",

"baseUrl": "src",

"paths": {

"*": ["node_modules/*"],

"src/*": ["./*"]

}

},

"include": ["src/**/*"]

}

Add ./tsconfig-paths-bootstrap.js file. This will allow us to properly build our app using root import alias:

/* eslint-disable @typescript-eslint/no-var-requires */

const tsConfig = require('./tsconfig.json');

const tsConfigPaths = require('tsconfig-paths'); const baseUrl = './build'; tsConfigPaths.register({

baseUrl,

paths: tsConfig.compilerOptions.paths,

});

Edit “main”, “scripts” and “nodemonConfig” section of our ./package.json file:

"main": "build/index.js",

"scripts": {

"build": "tsc",

"start": "node -r ./tsconfig-paths-bootstrap.js .",

"start:dev": "node -r dotenv/config -r tsconfig-paths/register -r ts-node/register ./src/index.ts",

"dev": "nodemon",

"test:unit": "mocha --recursive -r tsconfig-paths/register -r ts-node/register -r source-map-support/register src/**/*.spec.ts",

"test:lint": "eslint --ext .ts ./src",

"test:lint:fix": "npm run test:lint -- --fix",

"test": "npm run test:lint && npm run test:unit"

},

"nodemonConfig": {

"ignore": [

"**/*.spec.ts",

".git",

"node_modules"

],

"watch": [

"src"

],

"exec": "npm run start:dev",

"ext": "ts"

}

Add a ./src/configuration/index.ts file where we will keep all our ENV variables:

export const PORT: string = process.env.PORT || '3001';

Add our main express app ./src/app.ts file:

import express from 'express';

import bodyParser from 'body-parser'; export const getApp = () => {

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));

app.use(bodyParser.json());



app.get('/api/v1/test', (_, res) => {

res.json({ ok: true });

}); return app;

};

Add our entry ./src/index.ts file:

import { PORT } from 'src/configuration';

import { getApp } from 'src/app'; const startServer = () => {

const app = getApp();

app.listen(PORT, () => {

console.log(`server started at

});

} catch (error) {

console.error(error);

}

}; try {const app = getApp();app.listen(PORT, () => {console.log(`server started at http://localhost:${PORT}`); });} catch (error) {console.error(error);}; startServer();

Add an example test at ./src/app.spec.ts file:

import { expect } from 'chai';

import request from 'supertest'; import { getApp } from 'src/app'; describe('/api/v1/test', () => {

it('works', async () => {

const app = getApp();

const res = await request(app).get('/api/v1/test');

const { ok } = res.body;

expect(res.status).to.equal(200);

expect(ok).to.equal(true);

});

});

Let’s run tests:

npm run test

And we can start our dev server (It will automatically restart on file changes):

npm run dev

Check in your browser if our test endpoint works http://localhost:3001/api/v1/test

We can now build and run our server to check if everything compiled properly:

npm run build && npm start

Let me know if you have any problems or you think this “basic” setup should cover some more stuff as well.

Everything I just showed you is available here as a ready to clone repo: