Trying out NestJS part 2: Creating REST endpoints

Introduction

In my last blog post, I talked about why I wanted to give NestJS a try and what project I would build in order to evaluate it.

In this blog post, I'd like to set up the CRUD endpoints for the Tasks concept.

Before going on

One of the main benefits of using NestJS is that it's very opinionated about the way you should structure your application. As a result, you'll end up with an application that's loosely coupled and highly testable without even thinking about it.

However, NestJS can be hard to apprehend (especially if you are a junior developer). In my opinion, it's very important that you understand what Dependency Injection (and broadly speaking, the SOLID principles) and DI containers are since NestJS relies heavily on those concepts.

Setting up new endpoints

The source code for this part of the project is available here: https://github.com/arnaud-cortisse/trying-out-nestjs-part-2.

Creating CRUD endpoints is not very exciting. It's actually very tedious because the code always look the same. Fortunately, NestJS provides a sweet tool to generate all the boilerplate required to setup REST endpoints.

Using the CRUD generator

Before using the CRUD generator, please install the following package:

npm install @nestjs/mapped-types

In the root of your project, just type in

nest g resource tasks

You will be prompted with several choices. In this case, we're interested in developing a REST API.

? What transport layer do you use? (Use arrow keys) ❯ REST API GraphQL (code first) GraphQL (schema first) Microservice (non-HTTP) WebSockets

Then, choose YES.

? Would you like to generate CRUD entry points? (Y/n)

You should have the following output:

CREATE src/tasks/tasks.controller.spec.ts (566 bytes) CREATE src/tasks/tasks.controller.ts (890 bytes) CREATE src/tasks/tasks.module.ts (247 bytes) CREATE src/tasks/tasks.service.spec.ts (453 bytes) CREATE src/tasks/tasks.service.ts (609 bytes) CREATE src/tasks/dto/create-task.dto.ts (30 bytes) CREATE src/tasks/dto/update-task.dto.ts (169 bytes) CREATE src/tasks/entities/task.entity.ts (21 bytes) UPDATE src/app.module.ts (312 bytes)

The NestJS CLI has just generated a lot of boilerplate code for you.

Let's have a closer look into some of the generated files.

tasks.service.ts

It's a service that's going to deal with the data storage and retrieval.

import { Injectable } from '@nestjs/common'; import { CreateTaskDto } from './dto/create-task.dto'; import { UpdateTaskDto } from './dto/update-task.dto'; @Injectable() export class TasksService { create(createTaskDto: CreateTaskDto) { return 'This action adds a new task'; } findAll() { return `This action returns all tasks`; } findOne(id: number) { return `This action returns a #${id} task`; } update(id: number, updateTaskDto: UpdateTaskDto) { return `This action updates a #${id} task`; } remove(id: number) { return `This action removes a #${id} task`; } }

@Injectable() is a TypeScript decorator. Decorating classes with @Injectable allows them to act as providers, that is, instances of classes that can be injected in other instances of classes (at runtime) via Dependency Injection.

tasks.controller.ts

It's a controller that's going to handle incoming requests related to Tasks.

import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common'; import { TasksService } from './tasks.service'; import { CreateTaskDto } from './dto/create-task.dto'; import { UpdateTaskDto } from './dto/update-task.dto'; @Controller('tasks') export class TasksController { constructor(private readonly tasksService: TasksService) {} @Post() create(@Body() createTaskDto: CreateTaskDto) { return this.tasksService.create(createTaskDto); } @Get() findAll() { return this.tasksService.findAll(); } @Get(':id') findOne(@Param('id') id: string) { return this.tasksService.findOne(+id); } @Put(':id') update(@Param('id') id: string, @Body() updateTaskDto: UpdateTaskDto) { return this.tasksService.update(+id, updateTaskDto); } @Delete(':id') remove(@Param('id') id: string) { return this.tasksService.remove(+id); } }

@Controller() is a TypeScript decorator. Decorating classes with @Controller, along with methods decorated with HTTP verbs (@Get(), @Post() etc.), allows you to declare the routing of your app.

You can also notice that the constructor expects an instance of TaskService (the class we talked about earlier). Behind the scene, an instance of the TaskService is going to be injected by the DI container (built in the NestJS runtime, see custom-providers).

tasks.module.ts

It's a module that's going to hold all the instances of classes related to the Tasks.

import { Module } from '@nestjs/common'; import { TasksService } from './tasks.service'; import { TasksController } from './tasks.controller'; @Module({ controllers: [TasksController], providers: [TasksService] }) export class TasksModule {}

@Module() is a TypeScript decorator. Decorating classes with @Module allows for regrouping and encapsulating code that's closely related.

It's also going to affect how the DI container behaves.

app.module.ts

The app module is the root module (see modules). It's created inside main.ts, at startup.

import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TasksModule } from './tasks/tasks.module'; @Module({ imports: [TasksModule], controllers: [AppController], providers: [AppService], }) export class AppModule {}

You can see the CRUD generator updated imports and now references TasksModule, which is required for our app to expose all the routes declared inside TasksController. TasksModule is going to be directly connected to the AppModule in the application graph.

Not putting TasksModule inside the imports array would cause your tasks routes not to be available.

Test current setup

Go in the root of your project and type in

docker-compose up --build

Once everything is up and running, go to http://localhost:3001/tasks and verify that your get the message "This action returns all tasks".

Final words

As you can see, setting up new REST endpoints with NestJS is extremely easy.

However, you might be thinking there are way too many files and concepts involved. While I think that's quite true for such a small project, keep in mind that NestJS shines best when dealing with medium to large size projects.

Arnaud Cortisse © 2022