A beginner's tutorial to Monorepo using NestJs and TCP Transport.

Table of contents

No heading

No headings in the article.

Before we begin, it is important to comprehend the following terminology. Microservice, TCP, and Monorepo

Microservice: The microservice architecture, commonly referred to as microservices, is an architectural approach that organizes an application as a series of services that are

  • Highly maintainable and testable

  • Loosely coupled

  • Independently deployable

  • Organized around business capabilities

  • Owned by a small team

The microservice architecture enables the rapid, frequent and reliable delivery of large, complex applications. It also enables an organization to evolve its technology stack.

Monorepo: A monorepo is a software-development technique in which the source code for several projects is kept in the same repository. "Mono" stands for "single," while "repo" is short for "repository."

TCP: Transmission Control Protocol, or TCP, is a communications standard that enables computer hardware and software to exchange messages over a network. It is made to transfer packets across the internet and make sure that data and messages are successfully sent through networks.

Now that you understand the terminology, let's get down to business

First, we need to install the Nest CLI.

npm install -g @nestjs/cli
or
yarn global add @nestjs/cli

Now you should have the nest command in your terminal. We can test that with:

nest --version

Now we can create a "standard" project with

nest new monorepo-tutorial

You can either choose if you want to use npm or yarn. Since I like to use yarn, I always select yarn.

To enable monorepo mode, start with the standard mode structure and add a project. A project can be an entire application (added to your workspace with the nest generate your-app-name command) or a library (added to your workspace with the nest generate library command). More details on these specific types of project components are provided below. The main point to note here is the act of adding a project to an existing standard mode structure that converts to monorepo mode. Let's look at an example.

Now that we have created a default project, we need to convert it to a monorepo. Nest makes it very easy. Just run the following command:

cd monorepo-tutorial
nest generate app my-app

At this point, nest converts the existing structure to a monorepo mode structure. This results in a few important changes. The folder structure now looks different.

The generate app schematic has reorganized the code - moving each application project under the apps folder, and adding a project-specific tsconfig.app.json file in each project's root folder. Our original monorepo-tutorial app has become the default project for the monorepo, and is now a peer with the just-added my-app, located under the apps folder.

Before we continue, we need to change the port of the my-app app.

Open apps/my-app/src/main.ts and change the line:

await app.listen(3000);

to

await app.listen(4000);

now let's start our services.

nest start --watch

will start up the monorepo-tutorial app. To start my-app, we'd use:

nest start my-app --watch

Holla, Now we have two apps running in a mono repo!

Microservice

Now let's configure my-app as a microservice First, we need to add the Nest microservice package to our project.

yarn add @nestjs/microservices
or
npm i @nestjs/microservices

incase you experience this type of error

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: monorepo-tutorial@0.0.1
npm ERR! Found: @nestjs/common@8.4.7
npm ERR! node_modules/@nestjs/common
npm ERR!   @nestjs/common@"^8.0.0" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer @nestjs/common@"^9.0.0" from @nestjs/microservices@9.2.0
npm ERR! node_modules/@nestjs/microservices
npm ERR!   @nestjs/microservices@"*" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR! See C:\Users\DELL\AppData\Local\npm-cache\eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\DELL\AppData\Local\npm-cache\_logs\2022-11-14T13_58_19_483Z-debug.log

Run this package installation like this

yarn add @nestjs/microservices@8.0.0
or
npm i @nestjs/microservices@8.0.0

First, we need to edit apps/my-app/src/main.ts

import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { MyAppModule } from './my-app.module';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(MyAppModule, {
    transport: Transport.TCP,
    options: {
      port: 4000,
    },
  });
  await app.listen();
}
bootstrap();

NestFactory.create was changed to NestFactory. createMicroservice. Nest will learn from this that the app is now a microservice. We currently have a configuration JSON as well. Nest must be informed of the transport method we intend to use. The simplest one, TCP, does not require any other components. Redis, RabbitMQ, and many more are also options. I can go into further detail on that subject if there is significant interest in this article. The port must be added to the configuration JSON as well.

The second file we need to edit is apps/my-app/src/app.controller.ts.

We need to change

import { Controller, Get } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AddTodo } from './dtos/add.dto';
import { MyAppService } from './my-app.service';

@Controller()
export class MyAppController {
  constructor(private readonly myAppService: MyAppService) {}

  @MessagePattern('fetchAll')
  fetchAll(): unknown {
    return this.myAppService.fetchAll();
  }

  @MessagePattern('addTodo')
  addTodo(addTodo: AddTodo): string {
    return this.myAppService.addTodo(addTodo);
  }

  @MessagePattern('fetchOne')
  fetchOne(id: number): unknown {
    return this.myAppService.fetchOne(id);
  }

}

The third file we want to change is apps/my-app/src/app.service.ts We need to change

import { Injectable } from '@nestjs/common';
import { AddTodo } from './dtos/add.dto';

@Injectable()
export class MyAppService {
private readonly todos = [];

  fetchAll() {
    return this.todos;
  }

  addTodo(addTodo: AddTodo): any {
    const todo = this.todos.push({id: Math.floor(Math.random() * 10), title: addTodo.title})
    return {
      todo
    }
  }
  fetchOne(id: number) {
    return this.todos.find((item)=>item.id == id);
  }
}

That's it! Our my-app microservice is done.

In our monorepo-tutorial, we need to change the following file apps/monorepo-tutorial/src/app.service.ts to

import { AddTodo } from './dtos/add.dto';
import { Injectable } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';

@Injectable()
export class AppService {
  private client: ClientProxy;

  constructor() {
    this.client = ClientProxyFactory.create({
      transport: Transport.TCP,
      options: {
        port: 4000,
      },
    });
  }

  fetchAll(): Promise<unknown> {
    return this.client.send<string, string>('fetchAll', '').toPromise();
  }

  addTodo( addTodo: AddTodo): Promise<unknown> {
    return this.client.send<string, any>('addTodo', addTodo).toPromise();
  }

  fetchOne(id: string): Promise<unknown> {
    return this.client.send<string, string>('fetchOne', id).toPromise();
  }
}

This seems like a lot, all right! A constructor method that generated our client was introduced. The transport and port numbers used by the microservice we want to connect to must be disclosed to the client. Since this runs on the same system as the real-world app, we will omit the host parameter in this case.

All that is left to do is change our method to return a promise. Two parameters are passed to the send method. The name of the message we are sending is listed first, followed by the payload that we want to deliver.

The next file we need to change is apps/monorepo-tutorial/src/app.controller.ts to

import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { AppService } from './app.service';
import { AddTodo } from './dtos/add.dto';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/')
  fetchAll(): Promise<unknown> {
    return this.appService.fetchAll();
  }

  @Post('/')
  addTodo(@Body() addTodo: AddTodo): Promise<unknown> {
    return this.appService.addTodo(addTodo);
  }
  @Get('/:id')
  fetchOne(@Param('id') id: string): Promise<unknown> {
    return this.appService.fetchOne(id);
  }
}

Create a dto folder in the src directory of both my-app and monorepo-tutorial, i.e

apps/monorepo-tutorial/src/dtos/add-todo.ts
and
apps/my-app/src/dtos/add-todo.ts

If you want, you can also add the --watch flag to both commands so nest will rebuild the service every time you save.

Now we just need to open a postman and go to localhost:3000.

You should now be able to make such API request GET, POST

I hope you liked that post! If you want a follow-up, please comment, like, and share. So I can know that you are interested in content like that!

I hope you enjoyed reading it! Comment, like, and share if you want a follow-up. therefore I can understand your curiosity in such information!

Github Repo Link