How to Create Your First NestJS App


Author profile picture

@kernelpanic92Iacopo Ciao

Developer 👨🏼‍💻 | Pizza 🍕 | Sushi 🍣 | Lazy 🏋️‍♂️

In this article, I’m going to walk you through developing a simple todo rest application in NestJS and give you an overview of this framework.

What’s NestJS?

NestJS is a framework for a fast development of backend applications whose structure, inspired from Angular, makes it perfect for the realization of projects with MEAN stack.

https://nestjs.com/

When to use NestJS?

Nest is most suitable for developing applications that manage a huge quantity of requests with a tiny amount of data processed on the server side and prompt response time (e-commerce, live chats, etc..).

Why use NestJS?

The benefits of using NestJS are innumerable. The most important (imho) to highlight are:

  • Type checking: with Typescript we exploit all the power of javascript without running into type problems.
  • Modular: through the division into modules we can isolate our components based on the domain boundary.
  • Dependency Injection: thanks to the DI container we are able to write decoupled and testable code.
  • Adapters: Nest integrates all the most used and community tested javascript / typescript libraries and provides all the tools developers need to contribute to the collection.
  • Angular Like: the familiar structure allows us to easily switch from frontend to backend without having to change the development approach every time.
  • CLI: we can develop our functionality without having to worry about the initial boilerplate.

Let’s start writing code

  • retrieve the list of all todos
  • retrieve a single todo
  • insert a todo
  • edit a todo
  • delete a todo

therefore of the following APIs:

  • [GET] todos
  • [GET] todos/{id}
  • [POST] todos
  • [PUT] todos/{id}
  • [DELETE] todos/{id}

step 0: Setup

Setting up our project with two simple commands in terminal:

npm i -g @nestjs/cli
nest new TodoBackend

Run

npm run start

in todo-backend folder to make sure everything is okay!

step 1: Module

Modules are the mechanism that allows us to separate our components based on a domain they belong to and, in practice, to instruct the DI container about their interactions in the bootstrap phase.

The main module with which the bootstrap is performed is called root module and, in applications generated via CLI, we find it in the src folder under the AppModule name.

Our application, being very small and with a single functionality, could directly exploit the root module to manage its dependencies. There are at least two good reasons we won’t be doing this in this guide:

  • It’s not a typical case and consequently difficult to apply in a real context.
  • We would not be decoupled, thus losing the maintainability and portability of our code.

We create our module via CLI:

nest generate module todo

@Module({
  imports: [],
  providers: [],
  controllers: []
})
export class TodoModule {}

When it’s generated, the CLI will also take care of updating the AppModule by importing TodoModule as a feature module 🎉

step 2: Entity

An entity is a class that maps a table (or collection) of our database (very pragmatic definition).

We create our entity through CLI:

nest generate class todo/entities/Todo --no-spec
export class Todo {
  
  public id: number;
  public title: string;
  public completed: boolean;

  public constructor(title: string) {
    this.title = title;
    this.completed = false;
  }

}

step 3: Repository

Now that we have our entity we just have to persist it through an ORM!

For this guide I have decided to use Typeorm and setting up a basic connection to an sqlite database

First we install the dependencies:

npm i @nestjs/typeorm typeorm sqlite3

We modify AppModule by importing TypeOrmModule with the static method forRoot, inserting the configurations we need:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as path from 'path';
import { TodoModule } from './todo/todo.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'sqlite',
      autoLoadEntities: true,
      synchronize: true,
      database: path.resolve(__dirname, '..', 'db.sqlite')
    }),
    TodoModule
  ]
})
export class AppModule {}

Let’s add TypeOrmModule also on TodoModule, this time using the forFeature method, specifying Todo as the entity to manage:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Todo } from './entities';

@Module({
  imports: [
    TypeOrmModule.forFeature([Todo])
  ],
  providers: [],
  controllers: []
})
export class TodoModule {}

Now that we’ve configured Typeorm we can finally update our Todo entity with all the necessary annotations:

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

@Entity()
export class Todo {

  @PrimaryGeneratedColumn()
  public id: number;

  @Column()
  public title: string;

  @Column()
  public completed: boolean;

  public constructor(title: string) {
    this.title = title;
    this.completed = false;
  }
}

You can read further informations about Typeorm and its annotations by consulting the link attached at the beginning of the step.

For TypeOrmModule forRoot and forFeature methods, you can consult the database section in the official NestJS documentation: https://docs.nestjs.com/techniques/database

step 4: DTO

To avoid exposing our entities outside our business logic layer, we define a set of classes that will be used to manage the communication in and out of our services: the DTO (Data Transfer Objects).

export class AddTodoDto {

  public readonly title: string;

  public constructor(opts?: Partial<AddTodoDto>) {
    Object.assign(this, opts);
  }

}
export class EditTodoDto {

  public readonly title: string;
  public readonly completed: boolean;

  public constructor(opts?: Partial<EditTodoDto>) {
    Object.assign(this, opts);
  }

}
export class TodoDto {

  public readonly id: number;
  public readonly title: string;
  public readonly completed: boolean;

  public constructor(opts?: Partial<TodoDto>) {
    Object.assign(this, opts);
  }

}

step 5: Service

The service is the “package” where we are going to encapsulate our business logic, exposing a set of ready-to-use features.

Let’s define the objects that will populate our service layer:

nest generate service todo/services/todo

In the created service, we implement the findAll, findOne, add, edit and delete methods which, through the DTOs, will be consumed by our controller.

To decouple the conversion logic from Entity to DTO (and vice versa) from the business logic, let’s create a TodoMapperService:

nest generate service todo/services/TodoMapper
import { Injectable } from '@nestjs/common';
import { Todo } from '../../entities';
import { TodoDto } from '../../dto';

@Injectable()
export class TodoMapperService {

  public modelToDto({ id, title, completed }: Todo): TodoDto {
    return new TodoDto({ id, title, completed });
  }

}

Now let’s implement our TodoService: we inject, through Dependency Injection, the Todo Repository provided by Typeorm and our TodoMapperService:

import { isNullOrUndefined } from 'util';
import { Injectable, NotFoundException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Todo } from '../../entities';
import { TodoDto, AddTodoDto, EditTodoDto } from '../../dto';
import { TodoMapperService } from '../todo-mapper/todo-mapper.service';

@Injectable()
export class TodoService {

  public constructor(
    @InjectRepository(Todo) private readonly todoRepository: Repository<Todo>,
    private readonly todoMapper: TodoMapperService
  ) {}

  public async findAll(): Promise<TodoDto[]> {
    const todos = await this.todoRepository.find();
    return todos.map(this.todoMapper.modelToDto);
  }

  public async findOne(id: number): Promise<TodoDto> {
    const todo = await this.todoRepository.findOne(id);
    if (isNullOrUndefined(todo)) throw new NotFoundException();
    return this.todoMapper.modelToDto(todo);
  }


  public async add({ title }: AddTodoDto): Promise<TodoDto> {
    let todo = new Todo(title);
    todo = await this.todoRepository.save(todo);
    return this.todoMapper.modelToDto(todo);
  }

  public async edit(id: number, { title, completed }: EditTodoDto): Promise<TodoDto> {
    let todo = await this.todoRepository.findOne(id);

    if (isNullOrUndefined(todo)) throw new NotFoundException();

    todo.completed = completed;
    todo.title = title;

    todo = await this.todoRepository.save(todo);

    return this.todoMapper.modelToDto(todo);
  }

  public async remove(id: number): Promise<Todo> {
    let todo = await this.todoRepository.findOne(id);

    if (isNullOrUndefined(todo)) throw new NotFoundException();

    todo = await this.todoRepository.remove(todo);

    return todo;
  }

}

step 6: Controller

Here we are at the last layer of our climb to the NestJS stack! ⛰

To create our controller we will use our very useful CLI for the last time with this command:

nest generate controller todo/controllers/todo

Let’s implement the methods that will mirror the rest calls we listed at the beginning of the article, decorate them with routing annotations and hook them to the TodoService methods:

import { TodoService } from './../services/todo/todo.service';
import { TodoDto, AddTodoDto, EditTodoDto } from './../dto';

import {
  Controller, 
  Get,
  Param,
  Post,
  Put,
  Body,
  Delete
} from '@nestjs/common';


@Controller('todos')
export class TodoController {

  public constructor(private readonly todoService: TodoService) {}

  @Get()
  public findAll(): Promise<TodoDto[]> {
    return this.todoService.findAll();
  }

  @Get(':id')
  public findOne(@Param('id') id: number): Promise<TodoDto> {
      return this.todoService.findOne(id);
  }

  @Put(':id')
  public edit(@Param('id') id: number, @Body() todo: EditTodoDto): Promise<TodoDto> {
      return this.todoService.edit(id, todo);
  }

  @Post()
  public add(@Body() todo: AddTodoDto): Promise<TodoDto> {
      return this.todoService.add(todo);
  }

  @Delete(':id')
  public remove(@Param('id') id: number): Promise<TodoDto> {
      return this.todoService.remove(id);
  }

}

WARNING: DTO serialization is not active unless you decorate your controller method with

ClassSerializerInterceptor
@Post()
@UseInterceptors(ClassSerializerInterceptor)
public add(@Body() todo: AddTodoDto): Promise<TodoDto> {

In the next step we will deepen this topic by developing a solution that allows us to centralize this interceptor 😉

step 7: Validation

Our DTOs are ready to travel as fast as Formula 1 cars under the http protocol but one last piece is missing: the validation of its data.

To handle the validation of our fields, NestJS provides a validation pipe that takes advantage of the class-transformer and class-validator libraries. To be able to use it however, we need to install its dependencies in the project:

npm i class-transformer class-validator

Let’s add the ValidationPipe to the global pipes:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  await app.listen(3000);
}
bootstrap();

And now let’s decorate our DTOs:

import { IsNotEmpty } from 'class-validator';

export class EditTodoDto {

  @IsNotEmpty()
  public readonly title: string;

  public readonly completed: boolean;

  public constructor(opts?: Partial<EditTodoDto>) {
    Object.assign(this, opts);
  }

}

WARNING: Once our application has been compiled, all the DTOs we have defined so far will be converted into javascript objects, this means that no type check will be performed on the values of its fields!

So will our validators only work as long as they are passed the right type values? NO.

The class-validator library also has a set of validators specifically designed to type check our fields at runtime:

import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';

export class EditTodoDto {

  @IsString()
  @IsNotEmpty()
  public readonly title: string;

  @IsBoolean()
  public readonly completed: boolean;

  public constructor(opts?: Partial<EditTodoDto>) {
    Object.assign(this, opts);
  }

}

step 8: Run!

It’s time to run our application! 🎉

To execute it normally, just run the command:

npm run start

If we need to debug our code, we will have to run the command:

npm run start:debug

step 9: Enable CORS

After developing our backend, we will most likely want to develop a frontend application and try to consume our todo-backend, here the CORS comes into play:

Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. […] For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XmlHttpRequest and the Fetch API follow the same-origin policy.

https://developer.mozilla.org/en/docs/Web/HTTP/CORS

To enable CORS just edit main.ts again by invoking the enableCors() method with all the configuration parameters we need. For simplicity, we will enable everything! (Freedom! 🤟)

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(new ValidationPipe({ transform: true }));
  app.enableCors();

  await app.listen(3000);
}
bootstrap();

step 10: Test

Tests are absolutely necessary when you want to build a maintainable application: they prevent us from the most common development errors and possible regressions due to the modification of an existing functionality.

When we generate a new component via CLI, its test file is also automatically generated. Let’s try to make one:

import { Test, TestingModule } from '@nestjs/testing';
import { TodoService } from './todo.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Todo } from './../../entities';
import { repositoryMockFactory, MockType } from './../../../utils/test/repository.mock';
import { TodoMapperService } from './../todo-mapper/todo-mapper.service';
import { Repository } from 'typeorm';

describe('TodoService', () => {
  let service: TodoService;
  let repository: MockType<Repository<Todo>>;
  
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TodoService,
        TodoMapperService,
        { provide: getRepositoryToken(Todo), useFactory: repositoryMockFactory }
      ],
    }).compile();
    repository = module.get<Repository<Todo>>(getRepositoryToken(Todo)) as unknown as MockType<Repository<Todo>>;
    service = module.get<TodoService>(TodoService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('should throws exception when todo not exists', async () => {
    repository.findOne.mockReturnValue(Promise.resolve(null));
    await expect(service.findOne(1)).rejects.toThrow('Not Found');
  });

});

For test development, Nest uses Jest under the hoo

for more information see the NestJS documentation https://docs.nestjs.com/fundamentals/testing

You can find the sources of this article on my Github repository, feel free to put a ⭐️ 😁 https://github.com/KernelPanic92/nestjs-todo-ap

Conclusion

By creating the application described in this article, I was able to see firsthand how fast and powerful this framework is: it allows you to be extremely fast and flexible without having to give up absolutely anything. NestJS passes exams with top votes and can deservedly occupy its place in the top list of frameworks for web development.

If you enjoyed this guide, don’t forget to 👏 and share it with all the people who might benefit from it 😃

So long, and thanks for all the fish 🐬

Photo by Sereja Ris on Unsplash

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!

Don't forget to share

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *