Building REST API with Express, TypeScript and Swagger | Rishabh Mishra

Building REST API with Express, TypeScript and Swagger

03 October 2020

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, NodeJS is the most popular technology. I prefer using Express with NodeJS. 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, 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. 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.

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.

mkdir

express-typescript

cd

express-typescript

npm

init

-y

Install Typescript as development dependency

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 for more details.

tsconfig.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.

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

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

"scripts"

:

{

"build"

:

"tsc"

,

}

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

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 to see the JSON response.

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 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 which will watch the code and re-run the command on any changes.

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

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

"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.

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. 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.

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

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 in the browser. We can see the request gets logged in the terminal.

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

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

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

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.

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

{

"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

{

"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

"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

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

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/ to access the APIs documentation.

All the source code for this tutorial is available on GitHub.

Additional Resources

Next