Setup Nodejs API with Nestjs and Mikro-ORM

Setup Nodejs API with Nestjs and Mikro-ORM

Matthew Le
Matthew Le

Table of content

  • Initialize project with nestjs-cli
  • Add Config Module
  • Integrate Mikro ORM
  • Custom Exception handling
  • Build Simple module
  • Run and Build
  • Conclusion

Introduction

Beside Expressjs, Nestjs is the most popular framework for developer to build efficient, scalable Nodejs server side application. Nestjs supports multi paradigms: OOP, Functional Programing and Reactive Programing.

In this post, I guide you on how to create a Nestjs application, with the help of MikroORM, with Postgresql as database.

Prerequisites

  • Experience with NodeJS and Typescript
  • Postgresql database connection

Initialize project with nestjs-cli

First step we need to setup the nestjs CLI and create new nestjs project with nest cli, with the following command.

npm install -g @nestjs/cli
nest new nestjs-be-mikro

After we go through the installation process, cd to the created folder and start the server

cd nestjs-be-mikro
yarn start:dev

Add Config module

Application often run on many different environments. With each environment we need different configuration, beside that we also need to secure the secret values such as: database credentials, private key, ... We could get pass these values using environment variable, and access that through process.env in the code. But that would make the process of starting and application painful, so we utilize the package @nestjs/config to pass the configuration via .env file.

Install config package

yarn add @nestjs/config

Import config to app.module

import {ConfigModule, Config Service} from '@nestjs/config';
...
@Module({
  imports: [
    ConfigModule.forRoot(),
  ],
  controllers: [AppController],
  providers: [AppService],
})

Create config file

touch .env

Using config service


Now you can access configuration values through the ConfigService by import the ConfigModule and inject it using constructor injection.

// blogs.module.ts
@Module({
  imports: [ConfigModule]
  // ...
})
// blogs.service.ts
import {ConfigService} from '@nestjs/config';
// ...

constructor(private configService: ConfigService)

getDefaultCountry() {
  const country = this.configService.get<string>('DEFAULT_COUNTRY')
}

Access config service in main.ts

In main.ts we can not get  ConfigService via injection method, so in order to get ConfigService we can get it via app.get method

// main.ts

const app = await NestFactory.create(AppModule);
const config = app.get(ConfigService);

const port = config.get('PORT');

Mikro ORM

Installation

yarn add @mikro-orm/cli @mikro-orm/core @mikro-orm/nestjs @mikro-orm/postgresql

Integrate with NestJS

@Module({
  imports: [
    ConfigModule.forRoot(),
    MikroOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        entities: ['dist/entities/*.entity.js'],
        entitiesTs: ['src/entities/*.entity.ts'],
        type: 'postgresql',
        clientUrl: configService.get('POSTGRES_CONNECTION_STRING'),
        debug: process.env.ENV != 'production',
      }),
    }),
    BankAccountModule,
    AuthModule,
  ],
  // ...
})

Mikro ORM entity

In this setup is put entity files in ./src/entities folder.We define entity using reflect-metadata method.

We create an BaseEntity class that contains all common properties that appear on every entities.

// src/entites/BaseEntity.entity.ts

import { PrimaryKey, Property } from '@mikro-orm/core';

export abstract class BaseEntity {
  @PrimaryKey()
  id: number;

  @Property()
  createdAt: Date = new Date();

  @Property()
  updatedAt: Date = new Date();
}

Define Entity

// src/entities/User.entity.ts
import { Entity, Property } from '@mikro-orm/core';
import { BaseEntity } from './BaseEntity.entity';

@Entity()
export class User extends BaseEntity {
  @Property()
  phoneNumber: string;

  @Property()
  password: string;

  @Property()
  fullName: string;
}

Config Exception Handling

Nestjs support out-of-the box for exception handling by using built-in exception layer.But response format is not suit for my use case (response format in camelCase), so I override with my custom exception filter class

// src/exeptions.filter.ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost) {
    const { httpAdapter } = this.httpAdapterHost;
    const ctx = host.switchToHttp();
    const request = ctx.getRequest();
    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const responseBody = {
      status_code: status,
      path: request.url,
      message:
        exception instanceof HttpException ? exception.message : exception,
      error_detail: { code: 400 },
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, status);
  }
}

Modify main.ts file to using the new created exception filter

// src/main.ts
import { AllExceptionsFilter } from './exceptions.filter';

app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

// ..

With this setup now the response format would follow snake_case like this

{
  "status_code": 422,
  "message": "missing required value",
  "error_detail": {
    "code": 422,
    "message": "username field is missing"
  }
}

Build simple module

We use Nestjs module to separate the logic of of each feature.
The module we will create, includes 3 layers:

  • blogs.controller.ts: responsible for handling http request/response
  • blogs.service.ts: contains the domain logic of the module
  • blogs.module.ts: Organize relevant code for the feature

Service

Service layer contains related domain logic code of the feature. To create a service file we export a typescript class and decorate it with @Injectable annotation

```typescript
// src/modules/users/users.service.ts
/// ...
@Injectable()
export class BlogService {
  // ...
}
```

Our service class is useless if it cannot access to database connection or another services. So we use constructor injection to declare the dependencies for this service

import {InjectRepository} from '@mikro-orm/nestjs';
import {EntityRepository} from '@mikro-orm/postgresql';
import {Injectable} from '@nestjs/common';
import {Blog} from 'src/entities/Blog.entity';

@Injectable()
export class BlogService {
  constructor(
    @InjectRepository(Blog)
    private readonly blogRepo: EntityRepository<Blog>,
  ) {}

  const getAll() {
    return this.blogRepo.findAll();
  }

Controller


Controller responsible for http request coming from the client and response data. Just like Service class to create a controller we export a typescript class, but instead of decorate the class with @Injectable we use @Controller annotation

import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common';

@Controller('blogs') // this would create an api resource /blgos
export class BlogController {}
```

In the previous step we created a service class, to use in the controller, we need to use contructor inject like the code below:

```typescript
import { BlogService } from './blogs.service';

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

  @Get()
  async getAll() {
    return this.blogService.getAll();
  }

  @Get(':id')
  async get(@param() { id }) {
    return this.blogService.get(id);
  }
}

Module

To glue all the code together we need to create a module file.

// src/modules/blogs/blogs.module.ts
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Module } from '@nestjs/common';
import { Blog } from '../../entities/Blog.entity';
import { BlogsService } from './blogs.service';
import { BlogsController } from './blogs.controller';

@Module({
  imports: [MikroOrmModule.forFeature([Blog])],
  controllers: [BlogsController],
  providers: [BlogsService],
})
export class BankAccountModule {}

Config app module to use the new created module

// src/app.module.ts
import {BlogsModule} from './modules/blogs/blogs.module.ts'
import {BlogController} from './modules/blogs/blogs.controller.ts'
import {BlogsService} from './modules/blogs/blogs.module.ts'

@Module({
  imports: [
    //...
    BlogsModule
  ],
  controllers: [BlogsController],
  providers: [BlogsService]
})
export class AppModule

Run and Build

yarn start:dev # run in development mode
yarn build # build source
node dist/main.js # run project in production mode

Conclusion


In this post, we have successfully created a simple project with nestjs and mikro-ormFor more details please take a look at the official page of Nestjs and Mikro ORM.

References:

- [https://docs.nestjs.com/techniques/configuration](https://docs.nestjs.com/techniques/configuration)
- [https://mikro-orm.io/](https://mikro-orm.io)