Skip to main content

Getting Started with NestJS: A Beginner's Guide to Building Node.js Applications

Greetings!

The Node.js ecosystem is undergoing rapid evolution, making it challenging to choose the right framework or library. The essential step is to build a solid foundation in basic JavaScript and Node.js concepts. But I'm curious about the newest trends in development hence trying NestJS.

Nest Logo

What is NestJS?

Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications.
Nest provides an out-of-the-box application architecture that allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications.

Why is it worth trying

Nest shares similarities with Angular on the backend, drawing inspiration from Angular's concepts. For someone with a background in Java, like myself, it may feel reminiscent of working with the Spring framework. Getting started with Nest is straightforward and user-friendly. Apart from these points, there are some other aspects I'd like to emphasize.
NestJS offers several notable features:
  • A modular system that promotes clean and organized code structures.
  • Dependency injection, which facilitates decoupled and testable components.
  • Extensive use of TypeScript for strong typing and improved tooling support.
  • Flexibility to choose libraries like Fastify over Express for web server capabilities.
  • Embraces multiple programming paradigms, including Object-Oriented, Functional, and Reactive programming concepts.
  • Streamlined project setup and code generation through its CLI and schematics.

Basic concepts

Before diving into the sample project, it is worth mentioning a few useful concepts

Module

Each application has at least 1 module, the root module. We define features as feature modules and import them into the root module. These are the classes annotated with @Module decorator.
  • providers - the providers that will be instantiated by the Nest injector
  • controllers - the set of controllers defined in this module
  • imports - the list of imported modules

Controller

Controllers are responsible for handling incoming requests and returning responses to the client. These are annotated with the @Controller decorator.
  • Route handler decorators - it provides matching decorators for HTTP verbs, @Get, @Post, @Put, @Delete
  • @Body - route handler parameter decorator to access the request body
  • @Param - route handler parameter to obtain request parameters
  • @Query- route handler parameter decorator to obtain request query parameters
  • @HttpCode - route handler method decorator to denote HTTP status

Provider

Annotated with @Injectable decorator that marks as a class provider. The main idea of the provider is, that it can be injected as a dependency. This can be services, repositories, factories, helpers, and so on. Dependencies are injected into other classes via constructor injection using Nest's built-in dependency injection system.

Let's build something

The best way to learn a technology is to actually build something. Let's build a rest API to get started with Nest. Note that the idea is to quickly get started. Hence I'm not going to explain things in detail.

Setup

Nest comes with a powerful CLI which helps scaffold a project very quickly and generate files.
$ npm i -g @nestjs/cli
Once you installed Nest use the new operation to create a project
$ nest new nestjs-task-manager
$ cd nestjs-task-manager
I'm deleting some generated files and retaining only the main.ts and app.module.ts files while we create our feature module.
After this, let's generate modules, controllers, and services.
$ nest g module tasks
$ nest g controller tasks --no-spec
$ nest g service tasks --no-spec
$ npm install --save uuid
Let's create a model to hold our tasks in tasks/task.model.ts
export interface Task {
  id: string;
  title: string;
  description: string;
  status: TaskStatus;
}

export enum TaskStatus {
  OPEN = 'OPEN',
  IN_PROGRESS = 'IN_PROGRESS',
  COMPLETED = 'COMPLETED',
}
To handle the data transfer, we can use a DTO, tasks/task.dto.ts
import { TaskStatus } from "./task.model";

export class TaskDto {
  id: string;
  title: string;
  description: string;
}

export class TaskFilterDto {
  status: TaskStatus;
  search: string;
}
There's not a lot to elaborate on here. These are standard services and controllers, just like what you'd find in other programming languages.

tasks/tasks.service.ts
import { Injectable } from '@nestjs/common';
import { TaskDto, TaskFilterDto } from './task.dto';
import { Task, TaskStatus } from './task.model';
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class TasksService {
  private tasks: Task[] = [];

  findAll(taskFilterDto: TaskFilterDto) {
    let result: Task[] = this.tasks;
    const { status, search } = taskFilterDto;
    if (status) {
      result = result.filter(task => task.status === status);
    }
    if (search) {
      result = result.filter(task => task.title.includes(search) || task.description.includes(search));
    }
    return result;
  }

  create(taskDto: TaskDto): Task {
    const { title, description } = taskDto;
    const task: Task = {
      id: uuidv4(),
      title,
      description,
      status: TaskStatus.OPEN,
    };
  
    this.tasks.push(task);
    return task;
  }

  find(id: string): Task {
    return this.tasks.find(task => task.id === id);
  }

  delete(id: string) {
    this.tasks = this.tasks.filter(task => task.id !== id);
  }
}

tasks/tasks.controller.ts
import { Body, Controller, Delete, Get, HttpCode, Param, Post, Query } from '@nestjs/common';
import { TaskDto, TaskFilterDto } from './task.dto';
import { Task } from './task.model';
import { TasksService } from './tasks.service';

@Controller('tasks')
export class TasksController {
  constructor(private taskService: TasksService) {}

  @Get()
  getAll(@Query() taskFilterDto: TaskFilterDto): Task[] {
    return this.taskService.findAll(taskFilterDto);
  }

  @Get('/:id')
  getOne(@Param('id') id: string): Task {
    return this.taskService.find(id);
  }

  @Post()
  @HttpCode(201)
  create(@Body() taskDto: TaskDto): Task {
    return this.taskService.create(taskDto);
  }

  @Delete('/:id')
  @HttpCode(204)
  delete(@Param('id') id: string) {
    this.taskService.delete(id);
  }
}

tasks/tasks.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';

@Module({
  controllers: [TasksController],
  providers: [TasksService]
})
export class TasksModule {}

Start the application by executing the run command.
$ npm run start
By default, the server is listening to port 3000. We can access the endpoint and create, and list our tasks as below.
curl -X POST \
  http://localhost:3000/tasks \
  -d '{
	"title": "A task",
	"description": "Task description"
}'
That is it guys. Hope you enjoyed the Nest.
Happy coding ☺

References

Comments