avatarBrad Beighton

Summary

The provided content discusses the implementation of a microservice architecture using NestJS and gRPC for efficient and scalable application development.

Abstract

The article delves into the microservice architecture paradigm, advocating for the use of NestJS in conjunction with gRPC to build scalable, independent, and language-agnostic services. The author, after a previous exploration of NestJS's pros and cons, demonstrates how NestJS facilitates microservice development by providing an example repository. The advantages of microservices, such as independent deployment, scalability, and infrastructure risk mitigation, are weighed against the complexities in integration and increased testing demands. The article provides a step-by-step guide to creating an authentication microservice system, detailing the setup of Auth API and Auth Service, the use of NestJS modules, controllers, and services, and the definition of gRPC communication protocols with protobuf files. Emphasizing the importance of early setup for large applications, the author endorses NestJS for teams familiar with Angular, due to its similar structure and out-of-the-box functionality.

Opinions

  • The author believes that microservice architectures offer significant advantages for application development, including scalability and ease of management.
  • It is the author's view that NestJS is a suitable framework for building microservices due to its structure and functionalities, especially for teams with Angular experience.
  • The author acknowledges that while microservices can be complex to integrate and may require more resources, these challenges can be mitigated by using the right framework and development practices.
  • The author suggests that NestJS can reduce the learning curve and setup time for microservices, which can be beneficial for long-term project success.
  • There is an opinion that not all projects require a microservice architecture, and a well-built monolith may suffice for certain applications.
  • The author implies that early adoption of microservices can prevent future complications in scaling and managing an application's various components.

A Microservice Architecture with NestJS & gRPC

A Software Developer Sharing his knowledge — Generated by AI

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:

Nest can’t resolve dependencies of the AppService. Please make sure that the argument AUTH_PACKAGE at index [0] is available in the AppModule context.

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:

Development
Microservices
Nestjs
Grpc
Nodejs
Recommended from ReadMedium