Writing a Todos module in Nx monorepo Nest.Js application

February 12, 2020

In this article, we will be continuing our NestJs application from where we left.

NOTE: This article is a sequel to the article. I would recommend reading the article below before reading the current article. 
Introduction to NestJs with Nx Monorepo



After getting familiar with NestJs and the command to creating a new module in NestJs, we will be creating a Todos module that will be having a Todo Controller and a Todo Service.

Todos Module in NestJs

In the last article, we ended up creating a todos module in the NestJs application. The Todos module will be containing the complete code related to the todos like the controllers and the services.

In an empty NestJs app, we created a Todos module using the command

ng g @nestjs/schematics:module todos --path app --source-root apps/api/src

Which created the following folder in the NestJs project.

nest js and nx monorepo folder structure, todos module created

Creating a Controller in Todos Module

A controller is simply a class that handles the API requests and responses. It contains the endpoints for different actions like getting all todos, getting a todo by id creating a todo, etc. which we will look in a moment.

Command

The command for creating a Todos Controller is really simple. All you have to do is to replace the word module with the controller and update the --path flags value to the module in which the controller is to be made.

ng g @nestjs/schematics:controller todos --path app/todos --source-root apps/api/src

Pro Tip: Don't forget the -d flag that we discussed in the last article that dry runs the command without actually modifying the file and just shows the output.

This will create a todos.controller.ts file in the app/todos directory and update the todos.module.ts file to include the newly created controller.

command for creating a todos controller in a todos module, nx command, nestjs command


folder structure for todos module, nest js Nx Monorepo example for todos module


The inner todos folder contains our newly created controller.

If you don't like the folder structure of having a todos folder inside a todos folder, you are probably like me.

We can also get rid of this issue by setting another flag when creating the controller, but before doing that, simply delete the inner /todos folder and remove the imports from the todos.module.ts file.

Getting rid of inner todos folder

Now that we have deleted the inner todos folder, we will not create todos.controller.ts file adjacent to todos.module.ts file.

To create the controller again, simply run the last command with a - flat flag set to true. The  -- flat flag does not create a new folder.

ng g @nestjs/schematics:controller todos --path app/todos --source-root apps/api/src --flat true

This will create the following folder structure.

folder structure for todos module, nest js Nx Monorepo example for todos module

The todos.controller.ts file contains the following code.

import { Controller } from '@nestjs/common';
@Controller('todos')
export class TodosController {}

The @Controller decorator is what turns a simple TypeScript class into a NestJs controller. The parameter defines the access URL for the controller, for example, if the NestJs app is hosted at localhost:123/api then the todos controller will be accessed at localhost:1234/api/todos

Writing the todos controller

In Nest.Js, we do not need to directly deal with request and response objects as in ExpressJs. Instead, we simply write functions in controllers to support different HTTP requests like GET, POST, DELETE, etc. and decorate them with decorators imported from Nest.Js

/todos HTTP GET Endpoint

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

@Controller('todos')
export class TodosController {
    todos = [ 'Todo1', 'Todo2' ];
    @Get()
    getAll() {
        return this.todos;
    }
}

Now try running the application using the command

ng serve api

This will start the development server on our local environment. Look for the URL it is pointing and open that in the browser. In my case, it gave the output as

server started at port 3333, nest js server with Nx

To hit the getAll() function in the todos controller, we need to hit the URL

http://localhost:3333/api/todos

This will give the following response in the browser.

todos list api endpoint, nestjs and nx monorepo

We can also update the controller to include different HTTP endpoints. Update the todos.controller.ts file to the code below.

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

@Controller('todos')
export class TodosController {
  todos = ['Todo1', 'Todo2'];

  @Get()
  getAll() {
    return this.todos;
  }

  @Post()
  @HttpCode(204)
  add(@Body() data: { todo: string }) {
    return this.todos.push(data.todo);
  }

  @Get(':index')
  getByIndex(@Param('index') index: number) {
    if (this.todos[index]) {
      return this.todos[index];
    }
    return null;
  }
}

@Post() decorator converts a function to the controller's post request handler.

@HttpCode() decorator sets the custom success response code for the endpoint.

@Param() decorator is used to parse URL's with query params like /todos/1

Services in Nest.Js

Now that we have created the controller with some HTTP endpoints, but there is still one problem. We have written our business logic of manipulating todos in the controller itself. Instead, the business logic needs to be separated into services.

As the next step, we will be creating a todos service to remove all our business logic from the controller.

Creating todos service

To create a todos service, simply replace the word controller in the last command with the word service.

command to create todos service in NestJs Nx monorepo

This will create a todos.service.ts file inside the todos folder with the following content.

// todos.service.ts

import { Injectable } from '@nestjs/common';
@Injectable()
export class TodosService {}

Use of service

A service will contain the business logic for our applications. The controllers should only do the request and response handling and the validation part, the actual functionality should not be included in the controller to make it easily maintainable and extensible.

Moving the logic to todo.service.ts

Now that we have created a todo service, we will now move the business logic from the todo.controller.ts to the todo.service.ts.

The todo.service.ts file will handle the todos list CRUD operations.

Updating the todos.service.ts file

Update the service to include the following code.

import { Injectable } from '@nestjs/common';

@Injectable()
export class TodosService {
  todos: string[] = [];

  createTodo(todo) {
    this.todos.push(todo);
  }

  getAllTodos() {
    return this.todos;
  }

  getTodoByIndex(index: number) {
    if (this.todos[index]) {
      return this.todos[index];
    }
    return null;
  }
}

With the service code updated, we have the following codes in our controller and our service.

todos controller and service in NestJs Nx monorepo

Updating the todos.controller.ts file

Now we have written our business logic for todos manipulation in todos.service.ts file, we will remove the similar logic from our controller.

The best part of NestJs is that it comes with Dependency Injection inbuilt. The @Injectable decorator used on top of todos.service.ts file makes a service injectable into other services or controllers.

import { TodosService } from './todos.service';

export class TodosController {
...
  constructor(private todosService: TodosService) {}
...

With the constructor added, we now have to update the controller to use the service for handling todos.

The updated controller will look like:

import { Controller, Get, Post, Body, HttpCode, Param }
  from '@nestjs/common';
import { TodosService } from './todos.service';

@Controller('todos')
export class TodosController {

  constructor(private todosService: TodosService){}

  @Get()
  getAll() {
    return this.todosService.getAllTodos();
  }

  @Post()
  @HttpCode(204)
  add(@Body() data: { todo: string }) {
    return this.todosService.createTodo(data.todo);
  }

  @HttpCode(200)
  @Get(':index')
  getByIndex(@Param('index') index: number) {
    return this.todosService.getTodoByIndex(index);
  }
}

With the controller updated, the service and controller will look like:

NestJs folder structure best practice in Nx monorepo

Until this point, you are following best practices and standards. However, we are maintaining the Todos list in the main memory. We still need persistent storage. In the upcoming article, we will discuss
  • Writing Todos to a persistent storage
  • Make actual use of Nx monorepo to create code that will be shared across frontend and backend
  • Some more interesting concepts of Nx and NestJs

You Might Also Like

0 comments