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)