DEV Community

Bilal Haidar for This Dot

Posted on • Edited on

How to fly with Nest.js development

This article will show you how you can add database migrations, and data seeding, into your Nest.js application, and gain a super-fast development initiation phase.

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:

The source code of this article is available at this GitHub repo.

Let's start by creating the application.

Create a Full-Stack app with Angular CLI

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

Add an Angular app

Install the latest version of the Angular CLI globally by running the commands:

npm uninstall -g @angular/cli
npm cache clean --force
npm install -g @angular/cli@latest
Enter fullscreen mode Exit fullscreen mode

Now that you have installed the Angular CLI, let's create our Angular app by running this command:

ng new seeding-migration-typeorm

The ng new command asks you several questions as shown in Figure 1:

  • Whether you want to use Angular routing?
  • What style sheet format would you like to use?

After you make your selection, the Angular CLI starts scaffolding your application.

Once finished, open it in your preferred code editor, navigate to the package.json file, and change the start NPM script to look like this:

"start": "ng serve -o"

Let's take a squeeze, and run the application to make sure it works. Run the following command to start the application:

npm start

This command, which finishes building the application, opens a new browser instance, and runs the application as shown in Figure 2 below:

This is the new home page that the latest Angular CLI generates. Now let's focus on adding a backend Nest.js application.
Add a Nest.js app

The correct location to create a backend Nest.js application is inside the Angular root folder. The other option is to create it outside the Angular application, but to do so would be a nightmare! It is almost impossible to manage both with two separate package.json files. It's not recommended.

Nest.js provides the Angular Universal module for Nest framework that manages all the nifty little details of creating a new Nest.js application, and integrating it with the Angular CLI system.

Run the following schematic to create your Nest.js application:

ng add @nestjs/ng-universal

The schematic starts by installing the latest version of the @nestjs/ng-universal NPM package. It then prompts you for the client's app name. In this case, you type seeding-migration-typeorm.

This command creates a new Nest.js application inside of a new folder named server, and is placed at the root folder of the Angular app.

The command added a few NPM scripts into the package.json file, allowing you to compile, and run the new server app. In addition, it amends the angular.json file to add a new Architect Target name server.

Now, the easiest way to run this full-stack application with a live-reloading feature is to execute the following command:

npm run serve

Both the Angular and Nestjs apps would run on Port 4200. However, to access any Nest.js Controller endpoint, you will have to suffix the base URL with /api. The Nest.js backend API becomes available at http://localhost:4200/api.

Setup a PostgreSQL database with Docker

To demonstrate seeding, and migrations, we need to connect our backend application to a database engine.

For this article, I've chosen to use the PostgreSQL database engine or "Postgres". I will run an instance of PostgreSQL using a PostgreSQL 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:

version: '3'
services:
  db:
    container_name: typeormseed_db
    image: postgres:10.7
    volumes:
      - './db/initdb.d:/docker-entrypoint-initdb.d'
    ports:
      - '5432:5432'
Enter fullscreen mode Exit fullscreen mode

This docker-compose file instructs Docker to create a new Postgres Docker container with the following settings:

  • The container name is typeormseed_db
  • The Postgres Docker image postgres:10.7
  • Create a new volume by mapping the physical folder named db\initdb.d 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 Postgres container.
  • Finally, you expose the Postgres instance to the host machine by mapping its internal port to a port used on the host machine.

Now let's create a new folder named db at the root level of the Angular app. Then, create a subfolder named initdb.d. Then, inside this folder, create a new bash script file named initdb.sh. Place the following content inside this new file:

#!/usr/bin/env bash

set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE USER typeormseed_user;
    CREATE DATABASE typeormseed ENCODING UTF8;
    GRANT ALL PRIVILEGES ON DATABASE typeormseed TO typeormseed_user;

    ALTER USER typeormseed_user WITH PASSWORD 'password123';
    ALTER USER typeormseed_user WITH SUPERUSER;
EOSQL
Enter fullscreen mode Exit fullscreen mode

The first time Docker creates the Postgres container, it will run this script. It will then create a new Postgres database instance named typeormseed, a new database user named typeormseed_user, and finally gives the proper permissions for this user to run, and access, this database.

In addition, you need to add a new NPM script to the package.json file to make it easier when you run Docker, and instantiate a Postgres container.

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

"run:db": "docker-compose up -d && exit 0"

Finally, run the following command to start the container:

npm run run:db

This command will create the Postgres container in a detached mode.

Now that the Postgres 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.

You can consult the Nest.js official documentation on Working with databases in Nest.js.

You can also read a more detailed explanation on TypeORM, how Nest/js integrates with this library and examples on using them together by checking my article on Nest.js Step by Step Guide - Databases.

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 pg

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

Let’s import the TypeOrmModule inside the server/app.module.ts as follows:

import { Module } from '@nestjs/common';
import { AngularUniversalModule } from '@nestjs/ng-universal';
import { join } from 'path';

import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    AngularUniversalModule.forRoot({
      viewsPath: join(process.cwd(), 'dist/browser'),
      bundle: require('../server/main'),
      liveReload: true
    }),
    TypeOrmModule.forRoot()
  ]
})
export class ApplicationModule {}
Enter fullscreen mode Exit fullscreen mode

Next, create a new ormconfig.json file at the root of the Angular app, and paste the following content inside it:

{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode

At runtime, the TypeOrmModule class loads the configuration settings from the ormconfig.json file, and creates a TypeORM database connection to the Postgres 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[];
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

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> {}
}
Enter fullscreen mode Exit fullscreen mode

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

npx tsc -p server/tsconfig.json
npm run serve
Enter fullscreen mode Exit fullscreen mode

Run the next command to create our first migration. Make sure the Postgres 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"`);
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

Running query: SELECT
Query has been executed. Result: 
[]
Enter fullscreen mode Exit fullscreen mode

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> {}
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

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
  }
]
Enter fullscreen mode Exit fullscreen mode

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'] });
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

This post was written by Bilal Haidar, a mentor with This Dot.

You can follow him on Twitter at @bhaidar.

Need JavaScript consulting, mentoring, or training help? Check out our list of services at This Dot Labs.

Top comments (2)

Collapse
 
opensas profile image
opensas

Great article, how can you configure nest to run seed migrations ONLY when running in development mode?

Collapse
 
zajcman profile image
zajcman • Edited

Congratulations very professional article. I wanted to ask you something. Is there any way to launch nestjs API (server, installed with @nestjs/ng-universal ) in debug mode ?
Thanks for your answer