Setup Typescript for ExpressJS Project

Bill - 01 October, 2021
Reading time 3 mins

Some of you might already use Typescript on all of your projects, maybe for frontend application or backend application. There're also a few Nodejs frameworks that already support Typescript with no config required. I myself still using ExpresJS with plain Javascript, because some of my coworkers are not using Typescript or familiar with it, so that's the best choice for me. Typescript is really useful for us to prevent bugs in the development phase, yup, Type definitions do their job very well. But, not every Nodejs frameworks support it by default, in this case ExpressJS, we need to configure it to make it work in ExpressJS environment.

There are so many examples we can find in the internet about how to configure or using ExpressJS using Typescript, but in this article I'd like to share mine.

Prerequisite

We will start from scratch. First create a directory and install ExpressJS and Typescript packages for our project.

mkdir express-with-typescript; cd express-with-typescript; npm init -y; npm i express typescript ts-node tslib

The project structure for this project would be like the following example. Ignore it for now, please jump to the next step.

express-with-typescript/
  |__src/
    |controllers/
      |__index.controllers.ts
    |__interfaces/
      |__routes.interfaces.ts
    |__routes/
      |__index.route.ts
    app.ts
    server.ts
  nodemon.json
  package.json
  tsconfig.json

Create a Basic App

We will create a very basic example at the moment. Create 2 files, app.js and server.js.

// app.js =====
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('hello index');
});

module.exports = app;

// server.js =====
const app = require('./app');
const port = 3000;

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

We are done here, now let's start to configure Typescript for our simple app.

Configure Typescript

In our root directory, create a file called tsconfig.json. More info about this file, please check Typescript documentation.

{
  "compileOnSave": false,
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "allowJs": true,
    "baseUrl": "src",
    "declaration": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true,
    "lib": ["es2017", "esnext.asynciterable"],
    "moduleResolution": "node",
    "module": "commonjs",
    "noEmit": false,
    "outDir": "./dist",
    "pretty": true,
    "paths": {
      "@/*": ["*"],
      "@routes/*": ["routes/*"],
      "@interfaces/*": ["interfaces/*"],
      "@controllers/*": ["controllers/*"],
      "@utils/*": ["utils/*"]
      [...] more directory here
    },
    "resolveJsonModule": true,
    "sourceMap": true,
    "target": "es2017",
    "typeRoots": ["node_modules/@types"]
  },
  "exclude": ["node_modules"]
}

The compilerOptions is our main configuration. Inside that block, you can configure how the transpile works on our project. See the paths block in this example, you can mapping your modules inside that block, you can read TSConfig page for more detail.

Our project is not ready yet. Next, we need to install @types/node package. This package is used to load in all type definitions when using typescript in node, and we need to install additional packages for our project.

npm i -D @types/node @types/express tsconfig-paths

Now, we need to modify our previous code to become a Typescript files. Change .js to .ts, then update the content with the following code.

// app.ts ===================
import express from 'express';
import Routes from '@interfaces/routes.interface';

class App {
  public app: express.Application;
  public port: string | number;

  constructor(routes: Routes[]) {
    this.app = express();
    this.port = 3000;
    this.initializeMiddlewares();
    this.initializeRoutes(routes);
  }

  public listen() {
    this.app.listen(this.port, () => {
      console.log(`App listening on the port ${this.port}`);
    })
  }

  private initializeMiddlewares() {
    // more middewares here...
    this.app.use(express.json());
  }

  private initializeRoutes(routes: Routes[]) {
    routes.forEach(route => {
      this.app.use('/', route.router);
    });
  }
}

export default App;

// server.ts =====================
import App from '@/app';
import IndexRoute from '@routes/index.route';

const app = new App([new IndexRoute()])

app.listen()

As you can see, I am using mapping for modules, interfaces & routes, because I setup its paths (based on the project structure) inside compilerOptions. You can add more modules if you want, but do not forget to update your compilerOptions configuration. Now create that directories and its file.

mkdir -p express-with-typescript/{controllers/,interfaces/,routes/}
touch express-with-typescript/{controllers/index.controllers.ts,interfaces/routes.interface.ts,routes/index.route.ts}

Then, put these code on each files.

// index.controllers.ts
import { NextFunction, Request, Response } from 'express';

class IndexController {
  public index = (req: Request, res: Response, next: NextFunction): void => {
    try {
      res.send('hello index');
    } catch (error) {
      next(error);
    }
  };
}

export default IndexController;

// routes.interface.ts ==========
import { Router } from 'express';

interface Route {
  path?: string;
  router: Router;
}

export default Route;

// index.route.ts ==============
import { Router } from 'express';
import Route from '@interfaces/routes.interface';

class IndexRoute implements Route {
  public path = '/';
  public router = Router();

  constructor() {
    this.initializeRoutes();
  }

  private initializeRoutes() {
    this.router.get(`${this.path}`, () => {
      console.log('hello index')
    });
  }
}

export default IndexRoute;

We are almost done, before run our application, create nodemon.json file with the following configuration.

{
  "watch": ["src"],
  "ext": "ts,js",
  "exec": "ts-node -r tsconfig-paths/register --transpile-only src/server.ts"
}

And put a start script inside package.json file.

"scripts": {
  "start": "nodemon"
}

You can now run the application by npm run command, and try to access it through port 3000, you will see hello index text as the result. So, that's how I use Typescript in my node projects, I hope it's useful for you. By the way, you might want to consider a framework that's already support Typescript like NestJS, i strongly recommend it.

# javascript # typescript - Blog posts