It’s not easy to develop, test and deploy scalable applications. It’s best to follow a software development lifecycle (SDLC) and follow best practices. Looking at the capabilities of an application, a microservice-based architecture has gained popularity in recent years.
NestJS is a Node.JS framework based on ExpressJS, but it’s more than that. In fact, NestJS gives you everything you need to create microservices easily.
Also, read: NestJS vs ExpressJS: Which Framework To Choose?
This article will discuss microservice, how to create one and show how easy it is to use NestJS. There’s no need for frameworks or libraries other than what NestJS provides by default so that anyone can benefit from it.
What Is A Microservice?
Microservice is a software architecture pattern where a large, complex application is broken down into many small, independent processes. These individual services are built for maximum performance, scalability, and maintainability.
They’re also easier to develop in an Agile fashion because teams can work on individual services in isolation and then deploy them independently.
5 Advantages Of Building A Microservice
Some of the advantages of building a microservice are:
- Decoupled components — You can easily change or update a module without affecting other modules.
- Scalability — Microservices don’t share the same memory space, which makes it easy to scale your application and increase resources for a particular service when needed.
- Faster to build — Since you are splitting up your application into smaller pieces, you can start working on one module while still building other modules in parallel.
- Language and technology agnostic — You can use different frameworks and programming languages for each service if needed. This also lowers the barrier to entry since each team only needs to be proficient in one language or framework instead of having knowledge about all languages and frameworks used in the system.
- Reduces complexity — Microservices break down the application into smaller pieces that are easier to understand, maintain and test than a single monolithic codebase that does everything at once.
Why NestJS
NestJS is an open-source library for creating microservices with Node.js. It was created by the creators of the Express web application framework and further refined by their development team.
As you can imagine, NestJS is a perfect fit for building microservices, which are generally just a collection of small services that interact with each other to provide business functionality.
For example, NestJS can be used to quickly build a single app that provides all the functionality of several apps-allowing you to focus on your core business and not on writing boilerplate code for every little service you may need in your system.
Creating a Microservice with NestJS
Nest is an unopinionated, general-purpose framework for building backends. It can be used to build a monolithic app, a microservice app or a hybrid app. Nest uses TypeScript (a typed superset of JavaScript that compiles to plain JavaScript), which is quite easy to pick up if the developers have prior experience in languages such as Java or C#.
Also read: Monolith vs Microservices Architecture
Nest helps the developers use whatever libraries and tools you want within it, unlike other frameworks such as Meteor, where certain technologies are restricted from being used on the client side. This gives developers the freedom to pick and choose the right tools for your project, whether it’s Angular, React or Vue on the client side and MongoDB, MySQL or Postgres on the database side.
Creating your first application using Nest CLI
To start, download the NestJS CLI. The CLI helps bootstrap your new microservice so that it gets up and running quickly without having to first manually create a project folder or write out configurations from scratch.
$ npm i -g @nestjs/cli$ nest new nestjs-microservice
After you have created the necessary databases and the application is initialized, you’re going to use NestJS library to modify the boilerplate application and follow their protocol for creating the microservices using NestJS:
$ npm i — save @nestjs/microservices
Once installed, you can go ahead and replace the contents of your src/main.ts file with the code mentioned below:
Developers having a general understanding of NestJS can easily read through this file. The only unique part is the way they are initializing the application server. Instead of using the default NestFactory.create() method, developers use NestFactory.createMicroservice() which gives them more explicit control over what endpoints the application responds to and how it negotiates HTTP/HTTPS connections with its users:
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
options: {
host: ‘0.0.0.0’,
port,
},
});
In the above snippet, you’re declaring that you’re telling your microservice to listen to TCP requests on port (default is 8080). This means the service won’t be a REST API, but will respond to a more raw request format.
Now let’s take a look at the controller responsible for your routes and who responds to each method. First, you’re going to want to open up src/app.controller.ts and remove all annotations since microservices respond with TCP requests instead of HTTP.
import { Controller } from ‘@nestjs/common’;
import { MessagePattern } from ‘@nestjs/microservices’;
export class AppController {
@MessagePattern({ cmd: ‘hello’ })
hello(input?: string): string {
return `Hello, ${input || ‘there’}!`;
}
}
As mentioned above, how you define and annotate methods in your NestJS controllers is different from how you defined them when using Nest’s automatic code generation. Instead of using @Get(), @Post(), and other HTTP-specific annotations, you define your TCP interfaces using @MessagePattern() — an annotation that maps controller methods to incoming requests. You’ve defined the pattern to be any request that contains { cmd: ‘hello’}.
hello(input?: string): string {
return `Hello, ${input || ‘there’}!`;
}
Great! Now let’s make sure your microservice will start up. Your NestJS project comes pre-baked with a package.json file that includes all the appropriate commands for starting up your microservice locally, so let’s use that:
$ npm run start:dev
[5:41:22 PM] Starting compilation in watch mode…
[5:41:27 PM] Found 0 errors. Watching for file changes.
[Nest] 6361–04/31/2022, 5:41:28 PM [NestFactory] Starting Nest application…
[Nest] 6361–04/31/2022, 5:41:28 PM [InstanceLoader] AppModule dependencies initialized +20ms
[Nest] 6361–04/31/2022, 5:41:28 PM [NestMicroservice] Nest microservice successfully started +8ms
Microservice listening on port: 8080
Now, you’ve confirmed your service boots as expected, and now let’s look at creating a Dockerfile for the service. By creating this file, you’ll be able to create a portable, scalable image of your service that others can easily run without encountering any associated issues in different environments. This will mean that you can run it yourselves within a stable virtual environment, give it to team members so they can test it more efficiently, and deploy it to production-grade environments easily.
You’re going to base your Dockerfile and node-env on an open-source image of a file server. From there, you’ll install npm modules and run npm run build, which will transpile your TypeScript and minify the resulting code to optimize it.
Creating A Client Service
Creating a microservice is great, but that’s not enough. The best way to test your new service properly is to ensure it’s compatible with other services that might act as partners or peers.
To do this, you must learn how to create your microservices by starting with a NestJS project template, just like you did for the first one.
$ nest new client
You’re also going to install two additional NestJS libraries here. The first is the Config Library, a convenient way to parse and manage application variables. You’ll be using this later. The second is the Microservices Library, which contains several helper methods that make it easier to access other NestJS microservices.
You now have all of your required libraries installed so you can use them to build a client service for accessing the microservice you created earlier. In src/app.module.ts add the following:
The first thing that needs attention from the file contents above is the URL import. This allows ConfigService to be system-wide available for use in other areas of your application, like the application module:
imports: [ConfigModule.forRoot()];
Next, you’ll add the HELLO_SERVICE provider. This particular Service is where you can use Client Proxy Factory from the Nest Microservices Library to create a Service that works with other Services:
{
provide: ‘HELLO_SERVICE’,
inject: [ConfigService],
useFactory: (configService: ConfigService) => ClientProxyFactory.create({
transport: Transport.TCP,
options: {
host: configService.get(‘HELLO_SERVICE_HOST’),
port: configService.get(‘HELLO_SERVICE_PORT’),
},
}),
}
In the above excerpt, you’re registering a ClientProxy instance to the provider key HELLO_SERVICE listening on HELLO_SERVICE_PORT. These two values will load up appropriately from environment parameters. This type of parameterization is crucial when it comes to presenting an API that’s compatible with many different environments (like dev, staging, and production) without modifications to your codebase and sensitive information like passwords or private keys needs to be kept out of source control.
Now you’ve created an instance of your proxy, open up src/app.controller.ts and set it up by pasting the following code into it:
Here you’ll inject an instance of the client service into the controller. You’ve registered with Client for HELLO_SERVICE, so this is a key you use to designate which client class you want instances from:
constructor(
@Inject(‘HELLO_SERVICE’) private client: ClientProxy
) {}
With a client who points to your TCP microservice, you can start sending custom requests that match the @MessagePattern you defined in the service:
@Get(‘:name’)
getHelloByName(@Param(‘name’) name = ‘there’) {
// Forwards the name to your hello service, and returns the results
return this.client.send({ cmd: ‘hello’ }, name);
}
The above line listens to a GET request on /hello/:name and forwards the request to your downstream TCP-based microservice. It then returns the results.
Just like you did with the downstream microservice, let’s create a Dockerfile so the frontend service can be used to process payments. This new payment service should be able to communicate with other services in the system so you’ll continue to use NestJS as its backend language. Here is what your new Dockerfile looks like:
Running Both Services Together
As you may have noticed, you haven’t yet tested your new client service (TCP Service). Though it uses the same npm run start:dev command as your TCP Service the two don’t overlap. Why? Because your new client service wraps all of that code up in Meteor code, including some environmental variables that aren’t set anywhere in any configuration file but instead need to be assigned as environment parameters for your application to boot up successfully.
This means that deploying your new client service requires a few extra steps beyond just running npm run start:dev.
Let’s take the time to understand all about microservices. For starters, you could use your service on an architectural level. Copy the following into architect.yml file at the root of your TCP service project directory:
The above file does 3 things:
- Describes the name, keyword, and component description for others to discover and refer to.
- Outlines the services required by the component for operating.
- Introduces interfaces that others can use from outside of component.
The component is wired up and ready to go! You just need to add the manifest file. For now, you can create it manually in this location:
Before you know your TCP-based service could be deployed via Architect, let’s go ahead and create another component to represent your upstream REST API. Since this will be connecting to the previous one, you need it available to connect to via dependency. To do this, paste the following into another architect.yml file in the REST API project root directory:
Just like you’ve done with the previous component, let’s make sure you can deploy the new one.
As an example, deploying the TCP-service, your upstream HTTP service and enriching the networking so that both services are automatically talking to each other can be achieved by executing a command. Running the following command deploys examples/nestjs-simple-client locally and exposes the client interface at http://app.localhost/hello/world.
$ architect dev examples/nestjs-simple-client:latest -i app:client
Congratulations! That’s all it takes for a locally runnable component to be deployed to a remote cluster with Architect. Once that’s done, you will see the output from your application in the same output panel. There might be some latency from when the video screencast is being recorded and when the actual live application is deployed, but that should not take more than a few seconds.
Originally published at solutelabs.com.