Express with TypeScript setup
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:
- @types/body-parser
- @types/express
- @typescript-eslint/eslint-plugin
- @typescript-eslint/parser
- chai, @types/chai
- dotenv, @types/dotenv
- eslint
- eslint-config-prettier
- eslint-plugin-prettier
- mocha, @types/mocha
- nodemon
- prettier
- supertest, @types/supertest
- ts-node
- tsconfig-paths
- typescript
npm i -save-dev @types/body-parser @types/chai @types/dotenv @types/express @types/mocha @types/supertest @typescript-eslint/eslint-plugin @typescript-eslint/parser chai dotenv eslint eslint-config-prettier eslint-plugin-prettier mocha nodemon prettier supertest ts-node tsconfig-paths typescript
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 = () => { 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: