Nest js with TypeORM for API Development

Nest.js is a progressive Node.js Web Framework that provides you with a robust backend for your frontend applications. It is highly comparable to Angular in terms of concepts like Module, Provider, etc. and is a clear choice by Angular developers.

If you are interested, read more about Nest.js at:

In this section, I will create a full-stack application, using the Angular CLI, with the support of the Nest.js ng-universal library.

Basic Prerequisites

For this article, I've chosen to use the mysql database engine or "mysql". I will run an instance of mysql using a mysql Docker container, which I believe, is the cleanest, and easiest way to add a PostgreSQL database instance to your application.

Start by creating a new docker-compose.yml at the root of the Angular app and paste the following content inside it:


#  Run `docker-compose build` to build the images
#  Run `docker-compose up` to run the containers

version: '3.5'
services:
  db:
    image: mysql:5.7
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test
      MYSQL_USER: root
      MYSQL_PASSWORD: root
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - core_service_network
  apis:
    command: npm run debug
    build: ./trainer-io-apis
    ports:
      - 3000:3000
      - 5858:5858
    volumes:
      - ./trainer-io-apis/docker/node/node-docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
      - ./trainer-io-apis:/app
    env_file:
      ./trainer-io-apis/.env
    depends_on:
      - db  
    networks:
      - core_service_network    
networks:
  core_service_network:
    driver: bridge
    name: core_service_network

volumes:
  mysql_data:
    name: global_mysql
  apis_modules:   
    name: apis_modules   

My docker file for apis in nestjs

FROM node:carbon
WORKDIR /app

We can configure entrypoint for image bootstrap while initilize

  • trainer-io-apis/docker/node/node-docker-entrypoint.sh
#!/bin/sh
set -e

npm install
npm run build

if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ]; then
  set -- node "$@"
fi

exec "$@"

This docker-compose file instructs Docker to create a new mysql & node js Docker container with the following settings:

  • The container name is trainer-io-apis
  • The Docker image mysql
  • Create a new volume by mapping the physical folder named mysql_data to an internal folder inside the image. I will place an initialization script inside this folder so that Docker can run the first time it creates the mysql container.
  • Finally, you expose the mysql instance to the host machine by mapping its internal port to a port used on the host machine 3306 is our port and also we are exposing node js container port which is 3000

As we are passing environments variables for mysql containers, it will create test database with defined user root so we don't need to manually create database and users

Add the following script under the script node inside the package.json file:

Finally, run the following command to start the container:

  • docker-compose up &
  • docker logs portals_apis_1 --tail 50 -f

This command will create the node js and mysql container in a detached mode. Now that the mysql database is up and running, let's move on and continue adding more features.

Add TypeORM module

TypeORM is an Object Relational Mapping (ORM) library written in JavaScript and is capable of connecting to a variety of database engines including PostgreSQL, MySQL, Maria DB, MongoDB and much more.

Nest.js fully supports this library and provides the @nestjs/typeorm package as a wrapper around the TypeORM library to effortlessly integrate with the Nest.js Dependency Injection System.

To start using TypeORM in the Nest.js application, we need to install a few NPM packages. Run the command:

npm install @nestjs/typeorm typeorm mysql, mysql2

  • The @nestjs/typeorm package represents the Nest.js wrapper over TypeORM.
  • The typeorm package is the official TypeORM library package.
  • The mysql, mysql2 package is the official connector library for mysql.

Let’s import the TypeOrmModule inside the server/app.module.ts as follows:
Next, create a new ormconfig.json file at the root of the Angular app, and paste the following content inside it:

{
  "type": "mysql",
  "host": "localhost",
  "port": 3306,
  "username": "typeormseed_user",
  "password": "password123",
  "database": "typeormseed",
  "entities": ["dist//*.entity.js"],
  "migrations": ["dist/server-app/migration//*.js"],
  "synchronize": false,
  "cli": {
    "entitiesDir": "server",
    "migrationsDir": "server/migration"
  }
}

At runtime, the TypeOrmModule class loads the configuration settings from the ormconfig.json file, and creates a TypeORM database connection to the mysql database instance.

That’s it!

Build the Blog API

Now that the full-stack application is up and running with an active connection to the database, it’s time to start building the Blog API.

This next section is a step by step on how to:

  • Add a new module in Nest.js
  • Add model objects
  • Generate, and run migrations
  • Create migrations, and seed data
  • Add a Nest.js service
  • Add a Nest.js controller to test the application.

Let’s get started.

Add Blog module

Nest.js framework offers the Nest.js CLI. This component is similar to Angular CLI, or other CLI. The goal of the CLI, is to increase productivity by enhancing the software development process, and make it easier on the developer to add new Nest.js artifacts to the application.

Install the Nest.js CLI globally on your machine by running:

npm install -g @nestjs/cli

Back to the application, change directory cd to the server folder, and run the following command to scaffold a Blog Nest.js module:

nest g module blog --no-spec

The command creates a new blog module under the path /server/src/blog. In addition, it also imports this module into the main app.module.ts file.
Add model objects
We will create the Blog and BlogPost entity objects. Run the following command to create the two classes together:

touch src/blog/post.entity.ts src/blog/comment.entity.ts

Paste this code inside the blog/post.entity.ts file:

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Comment } from './comment.entity';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column('varchar')
  title: string;

  @Column('text')
  content: string;

  @Column({ type: 'varchar', nullable: true })
  tags: string;

  @Column({ name: 'created_at', default: () => `now()`, nullable: false })
  createdAt: Date;

  @Column({ name: 'updated_at', default: () => 'now()', nullable: false })
  updateTime: Date;

  @OneToMany(type => Comment, comment => comment.post, {
    primary: true,
    cascade: ['insert']
  })
  comments: Comment[];
}

The file defines the post entity to use to create a new post in the blog engine. It defines a one-to-many relation with the comment entity.

Paste the following inside the blog/comment.entity.ts file:

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { Post } from './post.entity';

@Entity()
export class Comment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column('text')
  content: string;

  @Column({ type: 'varchar', nullable: false })
  author: string;

  @Column({ type: 'varchar', nullable: false })
  email: string;

  @Column({ name: 'created_at', default: () => `now()`, nullable: false })
  createdAt: Date;

  @ManyToOne(type => Post, post => post.comments, {
    primary: true,
    cascade: ['insert']
  })
  post: Post;
}

The comment entity represents a single comment on a blog post. It defines a many-to-one relation with the post entity.

As a final step, let’s configure the TypeOrmModule with these entities at the level of the blog.module.ts file as follows:

@Module({
  imports: [TypeOrmModule.forFeature([Post, Comment])]
})
export class BlogModule {}

The TypeOrmModule will be able to generate custom repository classes for those entities, and make them available via the Nest.js Dependency Injection System as you will see shortly.
Generate, and run a migration
Now that the entities are ready, let’s generate a new migration with TypeORM.

A TypeORM single migration has two functions, up() and down(), that hold the code for a migration. The up() function runs when running a migration against the database, and usually contains code to create, or alter stuff, in the database. While the down() function runs when undoing a migration.

import { MigrationInterface, QueryRunner } from 'typeorm';

export class INITIALDB1566761690370 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<any> {}
  public async down(queryRunner: QueryRunner): Promise<any> {}
}

Start by building, and compiling the application, by running the following command:

npx tsc -p server/tsconfig.json
npm run serve

Run the next command to create our first migration. Make sure the mysql container is up and running.

npx typeorm migration:generate --name=INITIAL_DB

This command uses the TypeORM CLI to generate a new migration. It compares the state of the database with the entities it finds in the application, and generates the migration accordingly.

When you finish running this command, navigate to server/migration folder, and locate the new migration file there. It hosts the following migration content:

// 1566761690370-INITIAL_DB.ts

import { MigrationInterface, QueryRunner } from 'typeorm';

export class INITIALDB1566761690370 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<any> {
    await queryRunner.query(
      `CREATE TABLE "post" ("id" SERIAL NOT NULL, "title" character varying NOT NULL, "content" text NOT NULL, "tags" character varying, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_be5fda3aac270b134ff9c21cdee" PRIMARY KEY ("id"))`
    );
    await queryRunner.query(
      `CREATE TABLE "comment" ("id" SERIAL NOT NULL, "content" text NOT NULL, "author" character varying NOT NULL, "email" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "postId" integer NOT NULL, CONSTRAINT "PK_f685613ae59fda2ac1e490c9189" PRIMARY KEY ("id", "postId"))`
    );
    await queryRunner.query(
      `ALTER TABLE "comment" ADD CONSTRAINT "FK_94a85bb16d24033a2afdd5df060" FOREIGN KEY ("postId") REFERENCES "post"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
    );
  }

  public async down(queryRunner: QueryRunner): Promise<any> {
    await queryRunner.query(
      `ALTER TABLE "comment" DROP CONSTRAINT "FK_94a85bb16d24033a2afdd5df060"`
    );
    await queryRunner.query(`DROP TABLE "comment"`);
    await queryRunner.query(`DROP TABLE "post"`);
  }
}

The up() function shows the SQL code to create the two tables post, and comment. Also, it shows the code that alters the comment table to add a Foreign Key to the post Primary Key.

Once again, run the following command to build and compile the application:

npm run serve

Let's run the migration and create the corresponding tables in the database. Issue the following command:

npx typeorm migration:run

The command collects all the migrations that haven't been applied before on the database, and executes their up() function. In this case, we only have one migration to run.

The command creates the two tables and configures the relation between them.

To verify the migration, run:

npx typeorm query "SELECT * FROM post"

The result is the following:

Lets understand typeORM Migration First

Migrations

How migrations work

Once you get into production you'll need to synchronize model changes into the database. Typically it is unsafe to use synchronize: true for schema synchronization on production once you get data in your database. Here is where migrations come to help.

A migration is just a single file with sql queries to update a database schema and apply new changes to an existing database.

Let's say you already have a database and a post entity:

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

@Entity()
export class Post {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    text: string;

}

And your entity worked in production for months without any changes. You have thousands of posts in your database.

Now you need to make a new release and rename title to name. What would you do?

You need to create a new migration with the following sql query (postgres dialect):

ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name";

Once you run this sql query your database schema is ready to work with your new codebase. TypeORM provides a place where you can write such sql queries and run them when needed. This place is called "migrations".

Creating a new migration

Pre-requisites: Installing CLI

Before creating a new migration you need to setup your connection options properly:

{
    "type": "mysql",
    "host": "localhost",
    "port": 3306,
    "username": "test",
    "password": "test",
    "database": "test",
    "entities": ["entity/*.js"],
    "migrationsTableName": "custom_migration_table",
    "migrations": ["migration/*.js"],
    "cli": {
        "migrationsDir": "migration"
    }
}

Here we setup three options:

  • "migrationsTableName": "migrations" - Specify this option only if you need migration table name to be different from "migrations".
  • "migrations": ["migration/*.js"] - indicates that typeorm must load migrations from the given "migration" directory.
  • "cli": { "migrationsDir": "migration" } - indicates that the CLI must create new migrations in the "migration" directory.

Once you setup connection options you can create a new migration using CLI:

typeorm migration:create -n PostRefactoring

Here, PostRefactoring is the name of the migration - you can specify any name you want. After you run the command you can see a new file generated in the "migration" directory named {TIMESTAMP}-PostRefactoring.ts where {TIMESTAMP} is the current timestamp when the migration was generated. Now you can open the file and add your migration sql queries there.

You should see the following content inside your migration:

import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {
    async up(queryRunner: QueryRunner): Promise<void> {
    }
    async down(queryRunner: QueryRunner): Promise<void> {
    }
}

There are two methods you must fill with your migration code: up and down. up has to contain the code you need to perform the migration. down has to revert whatever up changed. down method is used to revert the last migration.

Inside both up and down you have a QueryRunner object. All database operations are executed using this object. Learn more about query runner.

Let's see what the migration looks like with our Post changes:

import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "title" TO "name"`);
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" RENAME COLUMN "name" TO "title"`); // reverts things made in "up" method
    }
}

Running and reverting migrations

Once you have a migration to run on production, you can run them using a CLI command:

typeorm migration:run

typeorm migration:create and typeorm migration:generate will create .ts files. The migration:run and migration:revert commands only work on .js files. Thus the typescript files need to be compiled before running the commands. Alternatively you can use ts-node in conjunction with typeorm to run .ts migration files.

Example with ts-node:

ts-node ./node_modules/typeorm/cli.js migration:run

This command will execute all pending migrations and run them in a sequence ordered by their timestamps. This means all sql queries written in the up methods of your created migrations will be executed. That's all! Now you have your database schema up-to-date.

If for some reason you want to revert the changes, you can run:

typeorm migration:revert

This command will execute down in the latest executed migration. If you need to revert multiple migrations you must call this command multiple times.

Generating migrations

TypeORM is able to automatically generate migration files with schema changes you made.

Let's say you have a Post entity with a title column, and you have changed the name title to name. You can run following command:

typeorm migration:generate -n PostRefactoring

And it will generate a new migration called {TIMESTAMP}-PostRefactoring.ts with the following content:

    "lint": "./node_modules/.bin/tslint --project ./tsconfig.json -e 'src/test/**/*.*'",
    "lint:fix": "./node_modules/.bin/tslint --project ./tsconfig.json -e 'src/test/**/*.*'  --fix",
    "typeorm": "ts-node ./node_modules/typeorm/cli.js",
    "dev": "ts-node-dev --respawn --transpileOnly src/index.ts",
    "typeorm:cli": "ts-node ./node_modules/typeorm/cli -f ./ormconfig.ts",
    "db:drop": "npm run typeorm:cli schema:drop",
    "migration:generate": "typeorm migration:create -n",
    "db:create": "ts-node src/scripts/create-database.ts",
    "db:revert": "npm run typeorm:cli migration:revert",
    "db:sync": "npm run typeorm:cli schema:sync",
    "db:migrate": "npm run typeorm:cli migration:run",
import {MigrationInterface, QueryRunner} from "typeorm";

export class PostRefactoringTIMESTAMP implements MigrationInterface {

    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`);
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`);
    }


}

See, you don't need to write the queries on your own. The rule of thumb for generating migrations is that you generate them after "each" change you made to your models.

Running query: SELECT
Query has been executed. Result: 
[]

The table is found but contains 0 records.

Seed data using migrations

So far you've seen how to generate a migration with TypeORM.

Now the plan is to use TypeORM to create a new empty migration. Then, we will fill this new migration with the code required to insert some static data. When the migration is ready, we will use the TypeORM CLI to run the migration against the database and seed the data into the tables.

This is how you would go about adding the seed data into the database using TypeORM.

Let's begin!

Run the command:
npx typeorm migration:create --name SEED_DATA

Replace the content of this file with the following:

import { MigrationInterface, QueryRunner, getRepository } from 'typeorm';
import { Post } from '../src/blog/post.entity';
import { Comment } from '../src/blog/comment.entity';

export class SEEDDATA1566763187470 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<any> {
    // create a post
    const post = getRepository(Post).create({
      title: 'Seeding data to database with TypeORM',
      content:
        'In this post, I will walk you through a step by step guide on how to seed data to database.'
    });

    await getRepository(Post).save(post);

    // create a comment
    const comment1 = getRepository(Comment).create({
      content: 'Nice job!',
      author: 'Jim Jones',
      email: 'jim.jones@myemail.com',
      post
    });

    // create a comment
    const comment2 = getRepository(Comment).create({
      content: 'Informative blog post!',
      author: 'Bin Nadal',
      email: 'bin.nadal@myemail.com',
      post
    });

    await getRepository(Comment).save(comment1);
    await getRepository(Comment).save(comment2);
  }

  public async down(queryRunner: QueryRunner): Promise<any> {}
}

The code uses the getRepository(post) function to get access to a repository instance for the post entity. Then, it uses the repository create() function to create a new post object together with the comments on this post.

It then uses the repository save() function to save the changes to the database.

To verify the results, run the following command:

npx typeorm migration:run
npx typeorm query "SELECT * FROM post"

The query results in the following:

Running query: SELECT * FROM post
Query has been executed. Result: 
[
  {
    "id": 1,
    "title": "Seeding data to database with TypeORM",
    "content": "In this post I will walk you through a step by step guide on how to seed data to database.",
    "tags": null,
    "created_at": "2019-08-25T17:39:11.917Z",
    "updated_at": "2019-08-25T17:39:11.917Z"
  }
]

There is a single post that exists in the database, and has an id = 1.

Let's also query for the comments by running the following command:

npx typeorm query "SELECT * FROM comment"

The query results in the following:

Running query: SELECT * FROM comment
Query has been executed. Result: 
[
  {
    "id": 1,
    "content": "Nice job!",
    "author": "Jim Jones",
    "email": "jim.jones@myemail.com",
    "created_at": "2019-08-25T17:39:11.953Z",
    "postId": 1
  },
  {
    "id": 2,
    "content": "Informative blog post!",
    "author": "Bin Nadal",
    "email": "bin.nadal@myemail.com",
    "created_at": "2019-08-25T17:39:11.974Z",
    "postId": 1
  }
]

Both comment objects were created with the postId = 1. All is working great!

This method of seeding data to a database with TypeORM using a migration is the best option available for you in the time being to populate your database with data during the development life cycle of your application.

Add the Blog service

Let's create the Blog service, and define functions to query for posts, and comments.

Change directory cd server to the server directory, and run the following command:

nest generate service blog --no-spec

This command creates the blog.service.ts file inside the blog folder and also adds this service as a provider on the blog.module.ts. This last step makes the BlogService available via the Nest.js Dependency Injection System.

Replace the content of this service with the following code:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Post } from './post.entity';
import { Repository } from 'typeorm';

@Injectable()
export class BlogService {
  constructor(
    @InjectRepository(Post)
    private repo: Repository<Post>
  ) {}

  async findAll(): Promise<Post[]> {
    return await this.repo.find({ relations: ['comments'] });
  }
}

The BlogService is decorated with @Injectable() decorator. This tells the framework that this service is expecting to receive dependencies through its constructor.

The service constructor injects a custom repository class for Post entity. The @nestjs/typeorm package is responsible for generating those custom repositories for all entities that we have already defined on the blog.module.ts by importing the TypeOrmModule.forFeature([Post, Comment]).

The service defines a single findAll() function that returns all posts stored in the database and eagerly loads their related comment objects too.

Add Controller

Now let's expose a Blog API Controller endpoint to allow client apps to request this data.

Change directory to the server folder cd server, and run the following command:

nest generate controller blog --no-spec

The command creates the BlogController class inside the blog/blog.controller.ts file, and also imports the controller into the controllers property of the blog.module.ts module. This last step makes the BlogController available via the Nest.js Dependency Injection System.

Replace the content of this controller with the following code:

import { Controller, Get, Req } from '@nestjs/common';
import { BlogService } from './blog.service';
import { Post } from './post.entity';

@Controller('blog')
export class BlogController {
  constructor(private readonly blogService: BlogService) {}

  @Get()
  async findAll(@Req() req: any): Promise<Post[]> {
    return await this.blogService.findAll();
  }
}

The BlogController class is decorated with the @Controller() decorator.

The constructor accepts a BlogService instance. The Nest.js Dependency Injection System creates, and provides, this controller a BlogService instance at run time.

This controller defines a single action designated by @Get() decorator to say that this action is accessed via a GET request. This action delegates the call to the BlogService to query for all post data in the database, and return them back in a response to the client request.
Let’s run the application and access this controller endpoint via the browser. Run the following command:

npm run serve

Navigate to the browser and type in the URL http://localhost:4200/api/blog and you shall see something similar to Figure 3 below:

Alt Text

Test with Postman

Let's test the BlogController using Postman this time.

With Postman, you can simulate a browser by sending HTTP requests, and receiving HTTP responses.

If you want to learn more about Postman, you can check my complete guide on Postman for busy developers.

Make sure the app is up and running, open Postman, and send a GET request to the URL http://localhost:4200/api/blog as shown in Figure 4 below:

Alt Text

Conclusion

At the beginning of every new project, you will be busy putting together the foundations of the application, building dummy interfaces to start showing the client, and getting some feedback. You won’t have enough time to build full services on the backend that provide all the CRUD (Create Read Update and Delete) operations right from the beginning to enable you to add data, and use it for testing purposes.

Using the TypeORM migrations to seed data into the database is a neat and quick workaround to get you started quickly. The added benefit of using these migrations is that they do not waste time on unnecessary activities in the early stages of the application.

Comments