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
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 lampeweb
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
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);
to
await app.listen(4000);
now let's start our services.
nest start
and in a second terminal
nest start blog
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
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();
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();
}
to
@MessagePattern('getHello')
getHello(name: string): string {
return this.appService.getHello(name);
}
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!';
}
}
to
@Injectable()
export class AppService {
getHello(name: string): string {
return `Hello ${name}!`;
}
}
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!';
}
}
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();
}
}
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();
}
}
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;
}
}
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
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
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)
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
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.
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).'
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?
+1
+1
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?
you can create an angular frontend with the nest cli.
the rest needs to be implemented manualy as far as i know.
Great article. Can You write more about alternative transport layers (Redis, RabbitMQ) and more about deploy nestjs apps and nestjs microservices on production? :)
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
+1
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 :)
plz do it!
Working on it 👍😊