DEV Community

Cover image for Monorepo and Microservice setup in Nest.js
Michael "lampe" Lazarski
Michael "lampe" Lazarski

Posted on • Updated on

Monorepo and Microservice setup in Nest.js

Let's first define what a monorepo is

a monorepo (a syllabic abbreviation of a monolithic repository) is a software development strategy where code for many projects is stored in the same repository.

Instead of heaving a git repo for every service or app, we will just have one git repo where everything will be included.

This style of organizing your code has some benefits.
Code reusability is easy since the code is already there. Updating dependencies is also more comfortable when you set up your mono repo correctly.

Now let's define what are 'microservices'.

Microservices is a software development technique. A variant of the service-oriented architecture (SOA) structural styleβ€” that arranges an application as a collection of loosely coupled services. In a microservices architecture, services are fine-grained, and the protocols are lightweight.

Instead of having everything in one application/service, we have minimal independent services. This can mean that every service has its database, can be written in a different programming language, and also should be easy to replace. Microservices are a hot and significant topic. Like everything, they have cons and pros! If you want to know more about microservices, write it down below in the comments, and I can write more about them in the next blog post!

Monorepo in nestjs

First, we need to install the Nest CLI.

npm install -g @nestjs/cli
or
yarn global add @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

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

nest --version
Enter fullscreen mode Exit fullscreen mode

Now we can create a "standard" project with

nest new lampeweb
Enter fullscreen mode Exit fullscreen mode

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

Now that we have created a standard project, we need to turn this project into a monorepo. This is pretty simple with nest we just need to run the following command:

cd lampeweb
nest generate app blog
Enter fullscreen mode Exit fullscreen mode

That's it! Now we have a monorepo. As you can see, the src folder is gone, and we now have an apps folder. In the apps folder, we can now find both of our applications/microservices.

One important file is the nest-cli.json file. When you open that file, you can see a JSON configuration file with a lot of entries. The import entries for us are "root": "apps/lampeweb",. This entry tells the Nest CLI where the main file of that project is. Also, you can find the "projects": { entry. Here we can find a list of every app/service in that project.

Before we can do that, we need to change the port of our blog app.

open apps/blog/src/main.ts and change the following line:

await app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

to

await app.listen(4000);
Enter fullscreen mode Exit fullscreen mode

now let's start our services.

nest start
Enter fullscreen mode Exit fullscreen mode

and in a second terminal

nest start blog
Enter fullscreen mode Exit fullscreen mode

So the first command will start our root app. In our case, this is the lampeweb app. The second command will start the blog service. Easy, right?

Now we have two apps running in a mono repo!

Microservices

The first thing we have to do is to add the Nest microservice package to our project.

yarn add @nestjs/microservices
Enter fullscreen mode Exit fullscreen mode

First, we need to edit apps/blog/src/main.ts

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

const logger = new Logger('Blog');

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.TCP,
    options: {
      port: 4000,
    },
  });
  await app.listen(() => logger.log('Microservice is listening'));
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

We changed NestFactory.create to NestFactory.createMicroservice. This will tell Nest that this app now is a microservice. We now also have a configuration JSON. We need to tell Nest what transport method we want to use. TCP is the simplest one and does not need any extras. We can also use Redis, RabbitMQ, and many more. If there is enough interest in this article, then I can go into more detail about that topic. We also need to add the port to the configuration JSON.

The second file in that app/service/microservice we need to edit is apps/blog/src/app.controller.ts.

We need to change

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
Enter fullscreen mode Exit fullscreen mode

to

  @MessagePattern('getHello')
  getHello(name: string): string {
    return this.appService.getHello(name);
  }
Enter fullscreen mode Exit fullscreen mode

Now we don't have an http verb anymore but MessagePattern. With the 'getHello' name we can later call that function.

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
Enter fullscreen mode Exit fullscreen mode

to

@Injectable()
export class AppService {
  getHello(name: string): string {
    return `Hello ${name}!`;
  }
}
Enter fullscreen mode Exit fullscreen mode

So that this getHello message accepts a string so that we can return the name back.
That's it! Our blog microservice is done.

In our lampeweb app, we need to change the following file apps/lampeweb/src/app.service.ts.
From

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

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

Enter fullscreen mode Exit fullscreen mode

to

import { Injectable } from '@nestjs/common';
import {
  ClientProxyFactory,
  Transport,
  ClientProxy,
} from '@nestjs/microservices';

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

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

  public getHello(): Promise<string> {
    return this.client.send<string, string>('getHello', 'Michael').toPromise();
  }
}

Enter fullscreen mode Exit fullscreen mode

Okay, this looks like a lot! We added a constructor method that created our client. We need to tell our client what transport and what port the microservice we want to connect to uses. In a real-world app, you also need to provide the host, but we are skipping that here because this runs on the same machine.

We now just need to modify our getHello method to return a promise. The send method takes two parameters. The first one is the name of the message we are sending, and the second one is the payload we want to send. In our case, just a simple string with a name.

The next file we need to change is apps/lampeweb/src/app.controller.ts from

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

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

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
Enter fullscreen mode Exit fullscreen mode

to

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

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

  @Get()
  async getHello(): Promise<string> {
    const helloValue = await this.appService.getHello();
    return helloValue;
  }
}

Enter fullscreen mode Exit fullscreen mode

Not much has changed just that we are now expecting a promise to be resolved to a string.

That's it!
Now you need to restart both services:

nest start
#and
nest start blog
Enter fullscreen mode Exit fullscreen mode

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 browser and go to http://localhost:3000/.

You should now see the following
Alt Text

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!

πŸ‘‹Say Hello! Instagram | Twitter | LinkedIn | Medium | Twitch | YouTube

Top comments (14)

Collapse
 
ivanhdzd profile image
IvΓ‘n HernΓ‘ndez DurΓ‘n

Great content!
I'm starting with NestJS, reading your content I'm developing a NestJS monorepo with microservices and adding custom shared libraries.
I cannot found informtation about how to build monorepo for production.
My shared libraries only works in root app (my Gateway) from my monorepo, but un my another apps (auth and API microservices) it Is not found.
Someone has a similar issues?
Regards
More description about my problems here:
github.com/nestjs/nest/issues/4984

Collapse
 
oaksoe profile image
Oak Soe Kyaw

Nice Article!

I have Nest microservices in different repos. Planning to use monorepo, but one thing struck my mind which is, each app under src don't have package.json, unlike lerna + yarn workspace.

Then, how will docker work? Let's say microserviceA has dependencies 1,2,3 and microserviceB has dep 4,5. With this setup, microserviceA container will have all the dependencies inside and same for B.

Collapse
 
oaksoe profile image
Oak Soe Kyaw

I think will need to use lerna or yarn workspace.

According to this: github.com/nestjs/nest-cli/issues/533

'The philosophy behind Nest CLI monorepo is different (it assumes that all shared libraries will exist in one repository, won't be published to any registry, and will be bundled together with the app).'

Collapse
 
manorinfinity profile image
Shivam Kumar

Loved the article. One quick question. In your setup we are running blog microservice on port 4000 is it possible to use port 3000 that is main service as an api gateway and blog as a tcp microservice does not allowing http requests on blog microservice but only on main api gateway?

Collapse
 
arrrrny profile image
Arrrrny

+1

Collapse
 
tomerl101 profile image
Tomer

+1

Collapse
 
faraazahmad profile image
Syed Faraaz Ahmad

I really like nest.js (due to a lot of reasons) but does it provide templating? or can i only make a decoupled backend with it?

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

you can create an angular frontend with the nest cli.

the rest needs to be implemented manualy as far as i know.

Collapse
 
pawlakartur profile image
Artur Pawlak

Great article. Can You write more about alternative transport layers (Redis, RabbitMQ) and more about deploy nestjs apps and nestjs microservices on production? :)

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Sure :) I will start soon on the other transport layers.

Still figuring out how to containerize the mini repo but once that is done there will be an article.

Also thinking about video versions

Collapse
 
janguianof profile image
Jaime Anguiano • Edited

+1

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

That sounds like a bigger project :D

I will think about it

specially DDD is not that easy to explain in a short blog post but I will thinking about it :)

Collapse
 
arrrrny profile image
Arrrrny

plz do it!

Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Working on it πŸ‘πŸ˜Š