Building REST API with Express, TypeScript and Swagger
Tags: node, express, typescript, swagger
Published: 2021-07-31 18:08:12

---
title: Building REST API with Express, TypeScript and Swagger
published: true
date: 2020-10-03 00:00:00 UTC
tags: Node, Express, TypeScript, Swagger
canonical_url: https://rsbh.dev/blog/rest-api-with-express-typescript
series: Building REST API with Express, TypeScript
---

I’ve started working with JS in 2017, since then I am writing frontend and backend code with it. It is easy to write web-server with NodeJS and I never found any serious performance issue in using NodeJS. According to [Stack Overflow 2020 survey](https://insights.stackoverflow.com/survey/2020#technology-other-frameworks-libraries-and-tools), NodeJS is the most popular technology. I prefer using [Express](https://expressjs.com/) with [NodeJS](https://nodejs.org/). It is one of the most popular Node.js web application frameworks. There are multiple frameworks, and you can choose whichever you want according to the need.

After working with [TypeScript](https://www.typescriptlang.org/), it became my preferred language of choice between JS and TS. TypeScript is the superset of JavaScript, means all valid JS is valid TypeScript. So it is easy to learn Typescript if you already knew JavaScript. TypeScript is 2nd most loved language according to the [Stack Overflow 2020 survey](https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-languages-loved). TypeScript helps you to add static types to the Javascript code. It is very helpful in writing, maintaining, and debugging code.

## What you will build

You will build REST API server with Express and TypeScript. It will generate production JavaScript code on `build` command. It will auto restart server on any code change during development, and it will auto generate OpenAPI documentation with [Swagger](https://swagger.io/).

## Bootstrap project

Let's create a directory with your preferred application name and set up an empty node project inside it. You can choose to customize package.json or accepts all of the default options by passing `-y` flag to `init` command.

```sh
mkdir express-typescript
cd express-typescript
npm init -y
```

Install Typescript as development dependency

```sh
npm i -D typescript
```

Add `tsconfig.json` in the root of the project directory. Here we define `outDir` as `./build` to put generated JavaScript files. You can put your preferred directory name. You can customize the config file more as per your need. Check [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for more details.

`tsconfig.json`

```json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./build",
    "strict": true,
    "esModuleInterop": true
  }
}
```

Install Express as dependency and type definitions of node and express as development dependencies.

```sh
npm i -S express
npm i -D @types/express @types/node
```

## Write server code

Let's add minimal code to make server up and running. Create a folder `src` inside the root folder. We will be going to put all the Typescript code inside it. It depends on personal choice. You can keep the code anywhere in the project.

This code will run the express server, listening to port 8000. It will add `/ping` route, which will reply JSON response on the GET call.

`src/index.ts`

```ts
import express, { Application } from "express";

const PORT = process.env.PORT || 8000;

const app: Application = express();

app.get("/ping", async (_req, res) => {
  res.send({
    message: "pong",
  });
});

app.listen(PORT, () => {
  console.log("Server is running on port", PORT);
});
```

Let's add the build command. it will transpile the TypeScript code into JavaScript and put the generated code in the output directory as mentioned in `tsconfig.json`.

`package.json`

```json
"scripts": {
  "build": "tsc",
}
```

Now let's build the JavaScript code with the build command.

```sh
npm run build
```

After running the above command we can see the JS code generated in the build folder. Now with node, we can run the server. We can visit [http://localhost:8000/ping](http://localhost:8000/ping) to see the JSON response.

```sh
node build/index.js

Server is running on port 8000
```

## Add development setup

The server is up and running. But still, development is difficult due to building and running the server manually after every code changes. It is better to automate this task. For this, we will use [ts-node](https://github.com/TypeStrong/ts-node) to run the typescript code directly, so then we don't have to run the typescript compiler during development. And to restart the ts-node on every code change, we will use [nodemon](https://nodemon.io/) which will watch the code and re-run the command on any changes.

Lets add ts-node nodemon as development dependencies in the project.

```sh
npm i -D ts-node nodemon
```

Now add the `dev` script to package.json, which will run the nodemon command. Add nodemon config to package.json. We can keep the config as a separate file. But I prefer to add it to package.json to keep the root of the project clean. Here we are configuring nodemon to watch all the `.ts` files inside the `src` folder and execute `ts-node src/index.ts` on any code change.

`package.json`

```json
  "scripts": {
    "build": "tsc",
    "dev": "nodemon",
  },

  "nodemonConfig": {
    "watch": [
      "src"
    ],
    "ext": "ts",
    "exec": "ts-node src/index.ts"
  }
```

After running the `dev` command, we can see the nodemon is running. And the server is up and running as well.

```sh
npm run dev

[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts
[nodemon] starting `ts-node src/index.ts`
Server is running on port 8000
```

## Add middlewares

Let's extend the server by adding some [middlewares](https://expressjs.com/en/guide/using-middleware.html). We are going to add three middleware to the server. `express.json` is built-in middleware to parse the request body, `express.static` is also built-in middleware used to serve the static files, and `morgan` is used to logs the requests. Let's install them as dependencies and their type definitions as development dependencies in the project.

```sh
npm i -S morgan
npm i -D @types/morgan
```

After installing the middleware, we can use them in the code. We will add them to the server with `app.use()` function. Here we make the `public` folder to serve the static files.

`src/index.ts`

```ts
import express, { Application } from "express";
import morgan from "morgan";

const PORT = process.env.PORT || 8000;

const app: Application = express();

app.use(express.json());
app.use(morgan("tiny"));
app.use(express.static("public"));
```

Now after running the server, open [http://localhost:8000/ping](http://localhost:8000/ping) in the browser. We can see the request gets logged in the terminal.

```sh
Server is running on port 8000
GET /ping 304 - - 2.224 ms
```

## Refactor

Till now the server is one single file. It is okay for small servers, but it is difficult to extend the server if it is one file. So we will create multiple files.

Let's create a controller for the ping request in `src/controllers/ping.ts` path. Here we add a class called `PingController` with method `getMessage`, we define the response interface with a property message as a string.

`src/controllers/ping.ts`

```ts
interface PingResponse {
  message: string;
}

export default class PingController {
  public async getMessage(): Promise<PingResponse> {
    return {
      message: "pong",
    };
  }
}
```

Now create a sub router in `src/routes/index.ts` file and move all the routing login there. In the server, we will add this sub router as a middleware.

`src/routes/index.ts`

```ts
import express from "express";
import PingController from "../controllers/ping";

const router = express.Router();

router.get("/ping", async (_req, res) => {
  const controller = new PingController();
  const response = await controller.getMessage();
  return res.send(response);
});

export default router;
```

`src/index.ts`

```ts
import express, { Application } from "express";
import morgan from "morgan";
import Router from "./routes";

const PORT = process.env.PORT || 8000;

const app: Application = express();

app.use(express.json());
app.use(morgan("tiny"));
app.use(express.static("public"));

app.use(Router);

app.listen(PORT, () => {
  console.log("Server is running on port", PORT);
});
```

## Swagger integration

Let's add OpenAPI documentation with the Swagger. We need to add `tsoa` to generates a JSON file with OpenAPI Specifications for all the APIs. We also need `swagger-ui-express` to host the Swagger JSON with Swagger UI.

```sh
npm i -S tsoa swagger-ui-express
npm i -D @types/swagger-ui-express concurrently
```

We need to add support for Decorators in the `tsconfig.json` file.

`tsconfig.json`

```json
{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
```

We need to create the config file for tsoa. Add `tsoa.json` at the root of the directory. Add `entryFile` and `outputDirectory` in the config. Here we are setting `public` as the output folder for the generated JSON file.

`tsoa.json`

```json
{
  "entryFile": "src/index.ts",
  "noImplicitAdditionalProperties": "throw-on-extras",
  "spec": {
    "outputDirectory": "public",
    "specVersion": 3
  }
}
```

We update the dev and build command to generate Swagger docs. We add `tsoa spec` to generate Swagger docs. We will be running the `swagger` command before build and dev command with `prebuild` and `predev` Respectively. We add `concurrently` to the dev command, which will run the nodemon and tsoa spec on parallel. The Swagger docs will get auto-updated on every code change during development.

`package.json`

```json
  "scripts": {
    "start": "node build/index.js",
    "predev": "npm run swagger",
    "prebuild": "npm run swagger",
    "build": "tsc",
    "dev": "concurrently \"nodemon\" \"nodemon -x tsoa spec\"",
    "swagger": "tsoa spec",
  },
```

Let's update the server file to serve the Swagger UI. We add `swagger-ui-express` to serve the Swagger UI for the hosted swagger JSON file.

`src/index.ts`

```ts
import express, { Application, Request, Response } from "express";
import morgan from "morgan";
import swaggerUi from "swagger-ui-express";

import Router from "./routes";

const PORT = process.env.PORT || 8000;

const app: Application = express();

app.use(express.json());
app.use(morgan("tiny"));
app.use(express.static("public"));

app.use(
  "/docs",
  swaggerUi.serve,
  swaggerUi.setup(undefined, {
    swaggerOptions: {
      url: "/swagger.json",
    },
  })
);

app.use(Router);
```

Now let's update the controller and add decorators to the class and methods to define the path and route for the API documentation. `tsoa` will pick the return type `PingResponse` as the response type for the `/ping` route.

`src/controllers/ping.ts`

```ts
import { Get, Route } from "tsoa";

interface PingResponse {
  message: string;
}

@Route("ping")
export default class PingController {
  @Get("/")
  public async getMessage(): Promise<PingResponse> {
    return {
      message: "pong",
    };
  }
}
```

After making all the changes and running the server, visit [http://localhost:8000/docs/](http://localhost:8000/docs/) to access the APIs documentation.

All the source code for this tutorial is available on [GitHub](https://github.com/rsbh/express-typescript).

### Additional Resources

- [Building a Node.js/TypeScript REST API, Part 1: Express.js](https://www.toptal.com/express-js/nodejs-typescript-rest-api-pt-1)

### Next

- [Building REST API with Express, TypeScript - Part 2: Docker Setup](https://rsbh.dev/blog/rest-api-express-typescript-docker)
- [Building REST API with Express, TypeScript - Part 3: PostgreSQL and Typeorm](https://rsbh.dev/blog/rest-api-express-postgres-typeorm)
- [Building REST API with Express, TypeScript - Part 4: Jest and unit testing](https://rsbh.dev/blog/rest-api-express-typescript-jest-testing)