A Microservice Architecture with NestJS & gRPC

Following on from my NestJS Pros and Cons article, I wanted to take a deeper dive into how NestJS can help you build a MicroService architecture out of the box. Microservice architectures are hugely popular and provide a lot of benefits to applications.
Microservices architecture (often shortened to microservices) refers to an architectural style for developing applications. Microservices allow a large application to be separated into smaller independent parts, with each part having its own realm of responsibility. To serve a single user request, a microservices-based application can call on many internal microservices to compose its response.
I just want to quickly touch on my personal pros and cons (although this isn’t a pro and con article). I’m fully aware that not everyone likes Microservice architectures and that’s fine.
Advantages
- Allows us to build, operate, and manage services independently, and we can easily scale them out based on the resources they need.
- Microservices take a lot of infrastructure risk out of the project straight away. With the infrastructure made almost invisible, microservice teams can iterate quickly.
- Each developer on a team can avoid getting tangled up in the underlying infrastructure, and focus on their piece of the project.
- If individual project modules don’t work exactly right together, it’s easy enough to isolate, disassemble, and reconfigure them until they do.
- Microservices offer language and platform freedom, so teams can choose the best language for the job at hand.
- Each Microservice can be deployed on its own, meaning deployment confidence.
Disadvantages
- Microservices are not the right solution for every project. A well-built monolithic system can scale just as well for some classes of problems.
- Integrating microservices can be quite complex.
- Microservices can also require increased testing complexity and possibly increased memory/computing resources.
- It’s possible to create un-scalable microservices if fundamental principles haven’t been followed.
For me, the disadvantages can be mitigated by the correct framework and by following the development practices.
What to do?
I’m going to cover a lot of code with a lot of moving parts, so I’ve gone ahead and created a public example repo that can be cloned, ran, and pulled apart where needed.
Firstly you need to decide what you’re building your Microservice architecture with and you’re probably wondering why even use a framework at all, and you’re right. You can absolutely build this type of architecture with NodeJS and there are TONS of great articles that talk about how to do it, but for reasons that I spoke about in my previous article, I’ve already chosen NestJS.
First things first, let’s create the apps. I’m using nx.dev so I already have multiple apps within 1 Github repo, however, multiple apps in multiple Github repos also work fine.
Below we’re going to make an auth-service which can be called from an auth-api the auth-api is publically reachable and communicates to the auth-service via gRPC communication.
Auth API
Firstly we want to set up the Auth API and bootstrap it to listen on port 3001 (any port is fine).
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3001);
}
bootstrap();Next, you’ll want to declare an endpoint that can be reached within the Auth API. NestJS gives us an out-of-the-box way to create an endpoint using a Controller file.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
}
}This opens up a get endpoint on the root of your API, so navigating to http://localhost:3001 will hit this endpoint. Next, we want to call your Microservice. The recommended approach is that your Controllers are only used for receiving calls, and all functionality should be executed via a Service file. Let’s set that up.
import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { lastValueFrom } from 'rxjs';
import { auth } from '@nestjs-grpc-microservice-example/protos';
@Injectable()
export class AppService implements OnModuleInit {
authService: auth.AuthServiceClient;
constructor(@Inject('AUTH_PACKAGE') private authClient: ClientGrpc) {}
onModuleInit() {
this.authService =
this.authClient.getService<auth.AuthServiceClient>('AuthService');
}
getData(): Promise<auth.User> {
return lastValueFrom(
this.authService.getUserById({
id: '1',
})
);
}
}There’s a lot going on here so let’s break it down. We want to set up a local variable for the Auth Service, we want to receive the Injected AUTH_PACKAGE that we’ll set up in the module, and then within an Init function we want to initialize the authService What you have now, is an application that’s set up to make gRPC calls to an Auth Service.
But first, you’re going to see the following error:

This means that you need to define AUTH_PACKAGE within the module.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { join } from 'path';
@Module({
imports: [
ClientsModule.register([
{
name: 'AUTH_PACKAGE',
transport: Transport.GRPC,
options: {
package: 'auth',
protoPath: join(__dirname, '/protos/auth/auth.proto'),
url: process.env.AUTH_SERVICE_URL ?? '0.0.0.0:6001',
},
},
]),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Auth Service
Now you want to create your second application which will not be publically accessible and only be reached via gRPC calls from the API. First, let's bootstrap the application and get it running on port 6001 (again this doesn’t matter).
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app/app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
package: 'auth',
protoPath: join(__dirname, '/protos/auth/auth.proto'),
url: '0.0.0.0:6001',
},
},
);
await app.listen();
}
bootstrap();Next, we want to create a controller that can receive the gRPC calls. NestJS’s package @nestjs/microservice gives us an out-of-the-box solution for this.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { GrpcMethod } from '@nestjs/microservices';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@GrpcMethod('AuthService', 'GetUserById')
getData() {
return this.appService.getData();
}
}Finally, as with the Auth API, want want to push the actual processing out to a service, keeping the controller as lightweight as possible.
import { auth } from '@nestjs-grpc-microservice-example/protos';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
constructor() {}
getData(): auth.User {
return {
id: '1',
email: '[email protected]',
firstName: 'John',
lastName: 'Smith',
};
}
}The gRPC Bit
So now you’ve got 2 applications that are set up to communicate with each other, but you’ve not actually given either of them the communication information, so let's do that. You’ll want to make a new auth.proto file in an area that is accessible by both applications, as I’m utilizing nx.dev I can simply make a Libs area that both applications can pull from.
The auth.proto should contain all functions and classes that can be called and returned by the gRPC communication. It's important that everything you want to return and call is specified.
syntax = "proto3";
package auth;
service AuthService {
rpc GetUserById(GetUserByIdData) returns (User) {}
}
message GetUserByIdData {
string id = 1;
}
message User {
string id = 1;
string email = 3;
string firstName = 4;
string lastName = 5;
}As you can see, we’ve defined a GetUserById function that accepts a GetUserByIdData class and returns a User class. Next, we want to parse this into something that our Typescript compiler can understand and utilize. I use protoc for this. It comes with a lot of compiler options which I won’t go into now. Using protoc we get a .ts generated file which gives us our types which can be used to ensure our methods are strongly typed.
Final Thoughts
This can look like a lot of code for a simple getUserById function and can come with a lot of upfront effort and what can seem like bloat, but if you know that your application is or will be large and will need areas of it to scale independently from others then setting up your Microservice architecture early will save a lot of headache down the line. I highly recommend NestJS if you’re team already has Angular experience as its structure is very similar and can reduce the initial learning curve of a new framework. It comes with a lot of functionality out of the box and as shown here, can help with difficult tasks such as setting up a Microservice Architecture. I’m fully aware that NestJS and Microservices aren’t right for everyone or every scenario, but specifically where Microservices are concerned, I personally believe that setting yourself up for success early and not underestimating what you’ll need from your API is important.
Drop me a follow or Subscribe to me for more like this or email me with anything you want me to talk about.
References
In Plain English
Thank you for being a part of our community! Before you go:
- Be sure to clap and follow the writer! 👏
- You can find even more content at PlainEnglish.io 🚀
- Sign up for our free weekly newsletter. 🗞️
- Follow us on Twitter(X), LinkedIn, YouTube, and Discord.




