DEV Community

Arsalan Ahmed Yaldram
Arsalan Ahmed Yaldram

Posted on

Setup express with Typescript - typeorm setup

Introduction

In this series we will setup an express server using Typescript, we will be using TypeOrm as our ORM for querying a PostgresSql Database, we will also use Jest and SuperTest for testing. The goal of this series is not to create a full-fledged node backend but to setup an express starter project using typescript which can be used as a starting point if you want to develop a node backend using express and typescript.

Overview

This series is not recommended for beginners some familiarity and experience working with nodejs, express, typescript and typeorm is expected. In this post which is part three of our series we will set up typeorm : -

  • Setup typeorm and connect to a Postgres Database.
  • Setup migrations and create a todo table.
  • Setup Todo Enitity.
  • Handle Graceful shutdowns.

Step One: Setup Typeorm

Lets start by installing the necessary dependencies : -

 npm install pg reflect-metadata typeorm
Enter fullscreen mode Exit fullscreen mode

In our app.ts we will import reflect-metadata just below our dotenv

import 'reflect-metadata'
Enter fullscreen mode Exit fullscreen mode

Lets update our .env and .env.sample with the following environment variables -

NODE_ENV=
SERVER_PORT=
RDS_POSTGRES_USERNAME=
RDS_POSTGRES_PASSWORD=
RDS_POSTGRES_HOST=
RDS_DATABASE_DEV=
RDS_DATABASE_TEST=
RDS_DATABASE_PROD=
Enter fullscreen mode Exit fullscreen mode

I am using AWS RDS Postgres Database so I have named my env variables as such. Copy the above env variables in your .env file and fill them up.

Now create a new folder under src called config and under src/config create a new file called dbConfig.ts and paste the following -

import 'dotenv/config'

export const dbConfig = {
  development: {
    host: process.env.RDS_POSTGRES_HOST,
    username: process.env.RDS_POSTGRES_USERNAME,
    password: process.env.RDS_POSTGRES_PASSWORD,
    database: process.env.RDS_DATABASE_DEV,
  },
  test: { 
    host: process.env.RDS_POSTGRES_HOST,
    username: process.env.RDS_POSTGRES_USERNAME,
    password: process.env.RDS_POSTGRES_PASSWORD,
    database: process.env.RDS_DATABASE_TEST,
   },
   production: {
    host: process.env.RDS_POSTGRES_HOST,
    username: process.env.RDS_POSTGRES_USERNAME,
    password: process.env.RDS_POSTGRES_PASSWORD,
    database: process.env.RDS_DATABASE_PROD,
   },
}
Enter fullscreen mode Exit fullscreen mode

We created an object where its keys are equivalent to our NODE_ENV values namely test, production and development. Take note we are using different database for each environment. Now under src folder create a new file datasource.ts and paste the following code : -

import { DataSource } from 'typeorm'

import { dbConfig } from './config/dbConfig'

const environment = (process.env.NODE_ENV || 'development')  as keyof typeof dbConfig

const credentials = dbConfig[environment]

export const AppDataSource = new DataSource({
  type: 'postgres',
  host: credentials.host,
  port: 5432,
  username: credentials.username,
  password: credentials.password,
  database: credentials.database,
  logging: true,
  synchronize: false,
  entities: [__dirname + '/**/*.entity.{js,ts}'],
  migrations: [__dirname + '/migrations/*.{js,ts}'],
})
Enter fullscreen mode Exit fullscreen mode

In the above snippet we create our AppDataSource we passed our credentials, we have set synchronize to false because we will be using migrations to create and delete databases. The entities key will search for files ending with .entity.ts or .entity.js. The migrations will be located inside the src/migrations folder.

Now lets import our Datasource in our main app.ts file and connect to the database. Inside the startServer method paste -

private startServer() {
 this.server.listen(this.port, async () => {
   console.log('Server started on port', this.port)
   try {
    await AppDataSource.initialize()
    console.log('Database Connected')
   } catch(error) {
    console.log('Error connecting to Database', error)
   }
 })
}
Enter fullscreen mode Exit fullscreen mode

Now run npm run dev from the terminal it should print Database Connected in the console. With that our Database connection is completed.

Step Two: Setup migrations and create a Todos table

Setting up migrations with typeorm is kind of tricky if you are using typescript in your node project. In your package.json paste the following under the scripts section :

  "typeorm": "typeorm-ts-node-commonjs -d ./src/datasource.ts",
  "migration:generate": "npm run typeorm migration:generate",
  "migration:show": "npm run typeorm migration:show",
  "migration:run": "npm run typeorm migration:run",
  "migration:revert": "npm run typeorm migration:revert",
  "migration:create": "typeorm-ts-node-commonjs migration:create"
Enter fullscreen mode Exit fullscreen mode

First lets create a migration folder, inside the src folder. Now to create a migration we run the following in the terminal :

 npm run migration:create src/migrations/todos
Enter fullscreen mode Exit fullscreen mode

This will create a new migration file inside of the migrations folder. Open the migration file, for the up function paste :

public async up(queryRunner: QueryRunner) {
 await queryRunner.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')
  await queryRunner.query(`
    create table todos(
      id uuid primary key not null default uuid_generate_v1(),
      text varchar(255) not null,
      status varchar(125) not null check (status in ('pending', 'done')) default 'pending')
`)
}
Enter fullscreen mode Exit fullscreen mode

We have 2 queries first to create an extension for uuid so that we can use uuid_generate_v1() this is postgres specific. Second query is a simple create table query. Now in the down function paste the following code :

public async down(queryRunner: QueryRunner) {
  await queryRunner.query('drop table todos')
}
Enter fullscreen mode Exit fullscreen mode

Now run the migration running the following command:

npm run migration:run
Enter fullscreen mode Exit fullscreen mode

And verify whether the todos table is created inside the database, also make sure you have the database before running the migration.

Step 3: Setup a Todo Entity

Lets now setup a typeorm todo entitiy. Inside the src/api/todos folder create a new file called todos.entity.ts and paste the following code :

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'

export enum TodoStatus {
  PENDING = 'pending',
  DONE = 'done'
}

@Entity()
export class Todos {
  @PrimaryGeneratedColumn()
   id: string

  @Column()
   text: string

  @Column({
   type: 'enum',
   enum: TodoStatus,
   default: TodoStatus.PENDING
  })
   status: TodoStatus
}
Enter fullscreen mode Exit fullscreen mode

Step Four: Handling Graceful shutdowns

It is very important to shutdown our node server and disconnect from our database gracefully. But what do we mean by graceful shutdowns ? When we deploy our node servers say on EBS, EC2 your server may go down. When our node server is killed, it should stop accepting new requests, complete all the current requests, we should then close our database connection and finally gracefully shutdown our node server / process. This video on youtube is a great explanation of this overall process, I recommend you to please watch this - https://www.youtube.com/watch?v=Z82mZV2Ye38.

In your app.ts lets add 2 new private functions like so : -

private handleServerShutDown() {
// Ctrl + C
 process.on('SIGINT', () => {
   console.log('SIGINT RECEIVED, SHUTTING DOWN')
   this.stopServer()
 })

// kill command
 process.on('SIGTERM', () => {
   console.log('SIGTERM RECEIVED, SHUTTING DOWN')
   this.stopServer()
 })
}

private stopServer() {
  this.server.close(() => {
   console.log('EXPRESS SERVER IS CLOSED')
    AppDataSource.destroy().then(() => {
     console.log('DATABASE DISCONNECTED')
     process.exit(0)
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

SIGINT is triggered on CTRL + C and SIGTERM is triggered when your process gets killed. In the stopServer() method we are closing our express server, destroying our database connection and finally exiting our node process using process.exit(0).

Now inside the constructor we will call our handleServerShutDown method

constructor() {
  this.server = http.createServer(expressServer)
  this.startServer()
  this.handleServerShutDown()
}
Enter fullscreen mode Exit fullscreen mode

Now we can start our node server by running npm run dev and then stop the server using CTRL+C you will see the console.logs from the shutdown functions.

Lets now build our project, by running npm run build and then run npm run start, everything should work as expected, check the console.logs and also the /api/todos routes.

Summary

Well then we have setup typeorm and todos entity, along with a migration. All the code for this tutorial can be found under the feat/setup-typeorm branch here. In the next tutorial we will use our TodoEntity in our controllers to fetch data from the database until next time PEACE.

Top comments (0)