Build an API Gateway with NestJs in 10 minutes
This article's intention is to give you a broader perspective into the Microservices architecture. There are many people out there, claiming they have a Microservice oriented architecture, but they lack the core concepts on which this pattern relies. My goal is to write a set of articles looking to clear all the fog that appears when shifting from monolithic, to highly distributed, applications.
The Microservices world is full of interesting and incredibly hard to implement stuff. When you get started, you think that, by just dividing your app in multiple services, you are already there. Sadly, that's almost never true. It's more common than you think to see people building highly critical apps this way, without having in place all the core concepts.
In this article, I'm going to focus on the API Gateway pattern. If you are doing Microservice architecture you SHOULD know it pretty well, being that the case use of this article is to make sure you have clear knowledge on these concepts. If you are enterily new to Microservices, have fun, and enjoy the ride.
In traditional monolithic applications, API clients consume everything from the same location. Although, once you start using microservices, things start to change. You may have multiple services running on entirely different locations.
What API Gateway means
The non deterministic nature of microservice architecture lead us directly to a whole new mess. But what can you do about it? One of the approaches out there is the API Gateway. From a 10,000ft view, it's just an extra service that you put in front of your other services so you can do composition of services.
The Problem
Let's say you have an application that consists of multiple services. We want to have our services' locations hidden from clients, so we'll have a proxy service that has to be able to compose multiple requests.
The Solution
We'll be using NestJs. If you havent used it already, you know that it's pretty similar to Angular, and I think it's a clever way to enable frontend developers to do things on the backend as well. Anyway, it comes out with a CLI tool that allows code generation.
In case you need it
Assuming you know NestJs, or that you've read the articles I've just given you, let's go ahead and start coding. But before we start, you'll need to install the NestJs CLI globally by executing the command npm install -g @nestjs/cli
.
Create the first service
In any microservices architecture, you'll find multiple services running, either in the same machine, or in totally distributed places. To start our small proof of concept, we'll create a service using the NestJs CLI. Just follow the next steps:
- Create a new folder, and go to it using your preferred command line tool.
- Execute
nest new service-a
. It will prompt you to choose between npm and yarn. I used npm. - Delete the files
src/app.controller.spec.ts
andsrc/app.service.ts
. - Remove the
AppService
usages from theAppModule
. - Remove the
AppService
usages from theAppController
.
The AppModule
will end up looking like this:
// src/app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
@Module({
imports: [],
controllers: [AppController],
providers: []
})
export class AppModule {}
The AppController
will end up looking like this:
import { Controller, Get } from "@nestjs/common";
@Controller()
export class AppController {
@Get()
getHello(): string {
return "hello";
}
}
You've got yourself your first service! Now is time to transform it into a microservice. Thankfully, NestJs covers a lot of it for you. By default, NestJs applications are generated as a server that uses HTTP as its transport layer. In the case of microservices, that's not what you want. When working with microservices, you are commonly using TCP instead.
If you are confused about HTTP or TCP, imagine they are just languages. A traditional Http Server talks in English, and a microservice using TCP talks in Spanish.
Since the service is structurally ready to be transformed to a microservice using NestJs, we'll do the next steps first:
- Go to the service folder using you preferred command line tool
- Execute the command
npm i --save @nestjs/microservices
- Update the entry point of the service
src/main.ts
with the service configuration - Update the
AppController
to use the Microservice Message pattern to serve clients
The entry point should end up looking like this:
import { NestFactory } from "@nestjs/core";
import { Transport } from "@nestjs/microservices";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";
const logger = new Logger();
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.TCP,
options: {
host: "127.0.0.1",
port: 8888
}
});
app.listen(() => logger.log("Microservice A is listening"));
}
bootstrap();
Are you wondering what's going on here? Let me explain it.
- We are using the
createMicroservice
instead of the defaultcreate
. - Now we have to provide an extra argument for the Transport and Microservice Options.
- Inside the microservice options, we tell NestJs the host and port we want to use.
NOTE: You can choose the host, and port of your preference. Also, NestJs has multiple transport options you can choose from.
The AppController
will end up looking like this:
import { Controller } from "@nestjs/common";
import { MessagePattern } from "@nestjs/microservices";
import { of } from "rxjs";
import { delay } from "rxjs/operators";
@Controller()
export class AppController {
@MessagePattern({ cmd: "ping" })
ping(_: any) {
return of("pong").pipe(delay(1000));
}
}
Instead of using the classic Get
decorator, we use the MessagePattern
. What this will do is trigger the ping
method when it receives a ping command. Then, it just returns the string pong after a second delay.
If you want to skip ahead, you can access this working version of create the first service.
Build the API Gateway
You have a new service to run, but how can you access it? That's what we are going to do next. We'll create a new service that works as a HTTP Server, and will map the request to the right service. This will look like a proxy that also allows you to compose requests, and reduce bandwidth usage in your application.
If you are wondering who uses this, AWS offers it as SaaS. Netflix even built their own solution.
Let's use your knowledge of the NestJs CLI:
- Go to the directory where
service-a
project is located using your preferred command line tool. - Execute
nest new api-gateway
. It will prompt you to choose between npm and yarn. I used npm. - Delete the files
src/app.controller.spec.ts
.
You are probably thinking, is that it? Well, no. But we are almost there. It's now time to hook the method we created.
- Go to the API Gateway root folder using your preferred command line tool.
- Execute the command
npm i --save @nestjs/microservices
. - Import the
ClientModule
and register theServiceA
. - Inject the new service into the
AppService
and create a method to query theServiceA
. - Use the new method from the
AppService
in theAppController
.
The AppModule
will end up looking like this:
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { AppService } from "./app.service";
@Module({
imports: [
ClientsModule.register([
{
name: "SERVICE_A",
transport: Transport.TCP,
options: {
host: "127.0.0.1",
port: 8888
}
}
])
],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
As you can see we need to setup the client to the service using the same transport and options but we give it a new property name
to identify the instance of the service. You can also create a custom provider in order to fetch its configuration either from a service that can be local or externally accesed using HTTP.
The AppService
will end up looking like this:
import { Injectable, Inject } from "@nestjs/common";
import { ClientProxy } from "@nestjs/microservices";
import { map } from "rxjs/operators";
@Injectable()
export class AppService {
constructor(
@Inject("SERVICE_A") private readonly clientServiceA: ClientProxy
) {}
pingServiceA() {
const startTs = Date.now();
const pattern = { cmd: "ping" };
const payload = {};
return this.clientServiceA
.send<string>(pattern, payload)
.pipe(
map((message: string) => ({ message, duration: Date.now() - startTs }))
);
}
}
What we are doing here is injecting the Client we imported in the AppModule
using its name as the token to identify it. Then, we create a simple method that gets the current time in milliseconds, sends a message to the service instance, and, once it gets a response, maps it to an object with the response message, and its total duration.
The AppController
will end up looking like this:
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get("/ping-a")
pingServiceA() {
return this.appService.pingServiceA();
}
}
If you start api-gateway
, and service-a
services, using npm run start:dev
, you'll be able to send a GET request to the API gateway by invoking http://localhost:3000/ping-a and get, as a response, an object with a message saying pong and the duration it took.
Although, this is not that impressive right? We could do this with a simple proxy. Things get slightly more complicated when you want to compose requests. But before we can do this, we'll need to create a new service. Go ahead and create the second service, and hook it on the API Gateway as I've just shown you.
If you want to skip ahead, you can access the api gateway with one service or the api gateway with the two services.
NOTE: In the second service, I used a delay of 2 seconds so we can see the difference between services available.
Composing Requests
We have everything in place- two services than can be running anywhere communicating through a single interface bringing more security and modularity to the application. But we want more. What if we had 12 services, and we had to do over 100 requests to fill all the information in a single page? Things will start to get out of hand.
We need a way to compose requests in the API Gateway. For this, I'm going to use some RxJs. The AppController
of the API Gateway will end up looking like this:
import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import { zip } from "rxjs";
import { map } from "rxjs/operators";
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get("/ping-a")
pingServiceA() {
return this.appService.pingServiceA();
}
@Get("/ping-b")
pingServiceB() {
return this.appService.pingServiceB();
}
@Get("/ping-all")
pingAll() {
return zip(
this.appService.pingServiceA(),
this.appService.pingServiceB()
).pipe(
map(([pongServiceA, pongServiceB]) => ({
pongServiceA,
pongServiceB
}))
);
}
}
The only new thing is the pingAll
method. If you havent seen RxJs before, this might look like some dark magic, but it's actually quite simple. We want to start the execution of our asynchronous calls at the same time, and consolidate all the responses into a single one.
NOTE: The zip method takes N observables and emits once all have emitted.
If you dont want to do any of this by yourself, just access this working version of the application.
Conclusion
And just like that, you got the API Gateway to compose requests for you. This is just a taste of what Microservices can do for your architecture. There are many more patterns, like API Gateway, that you can explore. A cool homework would be to create a new service that keeps track of the running services, and extending the imports using providers, to allow dynamically setting the client specification.
Top comments (23)
Great tutorial ! -
Just one question I am left with.
In this setup, is it the API-Gateway which connects to the other services (e.g. to service-a, service-b) ? Or are it the individual services (service-a, service-b) that connect to the API gateway service ?
From an architectural point of view, the 2nd solution seems like the way to go. i.e. auto-discovery and scalability of services. You need 1 more service? --> just launch it and let it tell about itself.
And secondly, in this setup, can there be multiple instances of a same service ? I think one of the goals of microservices is to have 0 downtime. My main drive to work towards microservices, is to have the ability to do easier upgrades. i.e. service-a is running but needs to be upgraded. I just leave it on, start the updated version of service-a in parallel. Both microservices connect to the same api-gateway at that stage, and then when it's up and running, I shutdown service-a. So for a short period of time both services are then active in parallel, assuring 0 downtime. - Well, at least that's how it's done in Java Spring Cloud solutions these days.
I am just curious about the limitations of the solution in your tutorial. It would be great if you could elaborate a bit about that.
Hi Bram, thank you for taking the time to go over the tutorial.
Regarding the first question, this implementation uses the first solution, I'm there with you, I believe the second solution is better from an architectural POV and it's achievable by using the Dynamic Modules technique you can find here docs.nestjs.com/fundamentals/dynam....
With the second question, you could do that as long as your microservices are behind a load balancer. As far as I can tell, there's no out-of-the-box solution for load balancing in NestJS but if they are behind a load balancer, the API Gateway won't really matter which instance it's working with.
I hope this clarifies your questions. I've got a ton of feedback with this article and I'm thinking on giving it a Part II, I'll take your suggestion on elaborating these limitations in it.
Thanks again for reaching out, feel free to ask any questions and sorry for the delay.
they are standalone -> they have a walkie talkie on the same channel. -> which is the tcp optionsn declared in the service it'self... The service creates this commjnication channel... Then we inject that service similar to if it was a database or a local class etc..
it's not ultimatly CONNECTED to the microservice there is simply a communication channel opened which can be connected to by the gateway -> or by other services ... or by the client or whatever you want, but it isnt able to control eachother, they just talk to eachother... if the pieces disconnect -> (as long as you handle your errors ) it wont have any real effect on the rest of the system (except for when one depends on the other.)
One last thing i forgot... it's not specific to nest... you could have something that was written in 2010 -> in Java -> or something written in any language -> that can access that connection... (im not entirely sure how simple this is or if it needs to use a sepperate transport method) but the conecpt is the same, it could be comming in from any authorized connection estabished on the service...
Could you make a more detailed example with CRUD ops on a real database? No one runs “hello world” in production
The idea of the article was to show how to interconnect multiple services by using NestJs Microservices framework. In my opinion there are plenty CRUD articles but it's a great idea for a future article to write one about it in the Microservices world.
Hi Daniel,
Great article. I need to integrate AWS API gateway which will be consuming my nestjs microservice. Is there a way we can directly make a call from AWS API gateway and can remove from here.
Thanks.
Hello my friend, I don't know how this comment slipped through.
To be entirely honest with you I'm far from being an AWS expert. From a 10,000 ft view, all you gotta do is creating a new endpoint and set it up to proxy the call to another domain where your NestJs service is running.
Hope that helps.
But how does this solution scale and what about single point of failure problem? Now it looks like you built a distributed monolith.
To learn more about monolith vs microservices you can read an article like this articles.microservices.com/monolit... this kind of architecture scales better but it's indeed harder to maintain.
What do you mean? Nest.js has been built for microservices. Otherwise you can use Java Spring Boot or IBM Websphere for monolithic stuff. The goal is to use microservices. This article about Nest.js is misleading and should not be presented as good practice. This is for lazy people, who make mistakes. There is no advantage in Nest.js. I have been in the game for too long. Why write article how they did it 20 years ago using new framework not built for that?
You say "Nest.js has been built for microservices", NestJs wasn't build only for microservices, it was build to fullfill the need of having a modular structure when writing NodeJs applications. Nevertheless, NestJs comes with a library to implement microservices easily, this article is based on their docs and my experience building this kind of service.
Then you say "There is no advantage in Nest.js", that depends on the context in which you're using it. I hear a lot of people complaining about using Javascript because they have been "too long" in the game, that's cool but I have services running with this in production and they're providing value to it's stakeholders. Each framework has a value, this one is specially good to get started coming from an Angular background.
"Why write article how they did it 20 years ago" things evolve and change, I get that some people like you hates that, but that's just how it is. If everyone was like you "git" wouldn't exist because "SVN" already does the job, and this is just a tiny example.
I feel like you have a strong opinion against NestJs, I'm just explaining how to use it for microservices because I've seen bad implementations of it with NestJs already. You won't find in the article anything saying "If you want to do microservices NestJs is your only choice", I clearly say "We'll be using NestJs. If you havent used it already, you know that it's pretty similar to Angular, and I think it's a clever way to enable frontend developers to do things on the backend as well". My content is mainly focused towards Angular developers.
goooo Danie!
NodeJs al estilo Angular, la neta o que?
si hahhaahaha que bonito
My question is can we implement both express application and microservice app in the same NEST.JS application? is it possible as I am handling my API Gateway elsewhere using something different! I want my NEST application to listen to outside HTTP requests and also listen to events emitted by other microservices,
Please suggest on it as I want both, I want my microservice to listen to outside HTTP requests also and also able to communicate with other services through ASYNC Communication like REDIS or KAFKA RABBIT MQ??
Hi Ayush, thanks for taking the time to go through it.
In theory, you can have an HTTP service AND a microservice inside the same project, in NestJs there's a
main.ts
file that works as an entry point, that's where you usually start the app (whether it's an express HTTP server or a microservice), you can modify the main.ts file so it runs both.Now, my recommendation is to have 2 NestJs applications:
This way you can "send stuff" to your Microservice using an HTTP Service. Is there a practical reason you can't have the two services?
Thanks Daniel! This looks so much like the typical REST Spring app... wow!
I'm glad you liked it!!!
Great tutorial !
But Nestjs offer the posibilty to use Hybrid Application that connect all microservices and can listen to coming http requests from an angular app for example.
The architecture is called "Backend for frontend" and in this architecture microservices can't communicate between each others
I have a scenario where I want a microservice to listen to HTTP Requests and also to the events emitted from the other service. How can I do that ?
You need a socket listening on a port and use the socket.on(event) to call the same method your controller is calling.
You can also use a message queue as entry point and make sure both your http calls and socket events dispatch a message, then you consume from your queue and execute the method. This approach is better in terms of scalability, you could later add a new endpoint in a different service and it will trigger the same behavior.
Thank you Daniel !