@duongtrungnguyen/nestro
TypeScript icon, indicating that this package has built-in type declarations

0.1.25 • Public • Published

Empower seamless microservices with effortless service discovery.

last-commit repo-top-language repo-language-count

Built with the tools and technologies:

Express JSON npm JavaScript TypeScript ESLint CSS

I. Overview

Nestro is a powerful service registry designed for NestJS applications. It streamlines the management and discovery of microservices by offering an HTTP-based pooling mechanism, real-time service monitoring, and a comprehensive dashboard for managing service dependencies. Nestro provides essential tools for efficient service registration and load balancing, tailored specifically for the NestJS ecosystem.

1. Features

  • Service registration:
    Services register themselves via HTTP requests. Regular heartbeats are sent to ensure that the registry remains updated with active instances.
  • Dynamic load balancing and service discovery:
    Distribute incoming requests using flexible strategies such as round-robin, random, or least-connections for optimal performance.
  • Robust security:
    Integrates key management and request validation to secure service communications using RSA algorithm.
  • Distributed Swagger:
    Auto merge Swagger documentation.
  • User-Friendly Dashboard:
    Monitor service instances in real-time, manage service dependencies, and perform administrative actions (e.g., deregistering services) directly through the dashboard.

2. How it works?

Service registration:

  • Bootstrap:
    Each microservice registers with the Nestro server upon startup by sending an HTTP POST request. The registration includes critical information such as the service name, host, port, and security details.
  • Storage:
    Nestro stores this information in its internal registry, making the service discoverable for other clients or services.
  • Heartbeat:
    To maintain an accurate registry, each service sends regular heartbeat requests. This mechanism allows the server to track active instances and remove those that no longer respond.

Service discovery:

  • When a client makes a request, Nestro will discover the available service instances based on the configured load balancing strategy. This ensures that traffic is distributed evenly and efficiently among all registered services.

II. Getting Started

Prerequisites

This project requires the following dependencies:

  • Programming Language: TypeScript
  • Framework: Nestjs
  • Platform: Express

Installation

Build nestro from the source and intsall dependencies:

Clone the repository:

❯ git clone https://github.com/duongtrungnguyenrc/nestro

Using npm:

❯ npm install @duongtrungnguyenrc/nestro@latest

Using yarn:

❯ yarn add @duongtrungnguyenrc/nestro@latest

Using pnpm:

❯ pnpm install @duongtrungnguyenrc/nestro@latest

III. Samples

1. Nestro server

Nestro needs an intermediary service to handle the service registry to avoid bottlenecks.

/* nestro-server/main.ts */

import { createNestroServer } from "@duongtrungnguyen/nestro";

import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await createNestroServer(AppModule, {
    security: {
      publicKeyPath: "~/keys/public.pem",
      privateKeyPath: "~/keys/private.pem",
    },
    enableRegistryDashboard: true,
  });
  await app.listen(4444);
}
bootstrap();

2. Nestro client applications

Nestro client is that the microservices will register with the nestro server and use pooling to periodically send heartbeats to notify the nestro server that the client instance is still active.

/* nestro-microservice/main.ts */

import { createNestroApplication } from "@duongtrungnguyen/nestro";

import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await createNestroApplication(AppModule, {
    server: {
      host: "localhost", // Server host
      port: 4444, // Server port (Default: 4444)
      secure: process.env.NODE_ENV === "production", // Server secure
    },
    client: {
      name: "client", // Service name
      host: "localhost", // Service instance host
      port: 3001, // Service instance port
      secure: process.env.NODE_ENV === "production", // Service instance secure
      heartbeatInterval: 10000, // Heartbeat interval in milliseconds
      swaggerJsonPath: "api-docs-json" // Service json Swagger document path
    },
    security: {
      privateKeyPath: "./private.pem", // private server generated key path
      publicKeyPath: "./public.pem", // public server generated key path
    },
    loadbalancing: {
      strategy: "round-robin", // load balancing strategy: [random, round-robin, least-connections]
      refreshInterval: 10000, // refresh interval in milliseconds
    },
    gateway: { // Use for api gateway service
      swagger: {
        path: "/docs", // Swagger document path
        title: process.env.APP_TITLE, // Swagger app title
        description: process.env.APP_DESCRIPTION, // Swagger app description
        version: process.env.APP_VERSION, // swagger app version
      },
    },
  });
  await app.listen();
}
bootstrap();

3. Gateway and communcations

For api gateway or communication between registered services. We using http proxy to handle proxy forwarding request to registered microservices.

NOTE: Nest js always using body parser by default and body request alway read complete before streaming. But http proxy need raw body for streaming and we need to open raw body mode on Nest application options.

import { createNestroApplication } from "@duongtrungnguyen/nestro";

import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await createNestroApplication(
    AppModule,
    {
      // ... Nestro application configs
    },
    {
      rawBody: true, // Enable raw body
    }
  );

  await app.listen();
}
bootstrap();

Now we can config route proxy using builder pattern. Nestro will automatically handle proxy request.

/* nestro-gateway/gateway.module.ts */

import { ProxyModule } from "@duongtrungnguyen/nestro";
import { Module } from "@nestjs/common";

@Module({
  imports: [
    ProxyModule.builder()
      .httpRoute({
        // proxy http request
        route: "/user/*", // Route to match
        service: "user", // Target registered service
        target: "", // Force target path
        pathRewrite: { "^/api/user": "/" }, // Rewrite path
        timeout: 10000, // Proxy timeout
        requestHooks: {
          guards: [], // guards,
          middlewares: [], // middlewares
        },
      })
      .wsRoute({
        // proxy ws request
        // ... same options with http route
      })
      .useGlobalMiddleware(/*... middlewares */)
      .useGlobalGuard(/*... guards */)
      .build(),
  ],
})
export class GatewayModule {}

Or using config class

import { IGatewayConfig, ProxyModuleBuilder } from "@duongtrungnguyen/nestro";
import { DynamicModule, RequestMethod } from "@nestjs/common";

export class GatewayConfig implements IGatewayConfig {
  configure(builder: ProxyModuleBuilder): DynamicModule {
    return (
      builder
        .httpRoute({
          // ... options
        })
        .wsRoute({
          // ... options
        })
        // ... builder config
        .build()
    );
  }
}
import { ProxyModule } from "@duongtrungnguyen/nestro";
import { Module } from "@nestjs/common";

import { GatewayConfig } from "@gateway.config";

@Module({
  imports: [ProxyModule.config(GatewayConfig)],
})
export class AppModule {}

Or using Proxy decorator

import { Proxy, ProxyTemplate, ProxyService } from "@duongtrungnguyen/nestro";
import { Controller, Get, Param } from "@nestjs/common";

@Controller("api")
export class ApiController extends ProxyTemplate {
  constructor(proxyService: ProxyService /* Inject from proxy module */) {}

  @Get("users")
  @Proxy({
    target: "https://api.example.com",
    pathRewrite: { "^/api": "" },
  })
  proxyUsers() {
    // Don't need code here, Nestro will auto handle proxy
  }

  @Get("products/:id")
  @Proxy({
    target: "https://products.example.com",
    pathRewrite: { "^/api/products": "/products-api" },
  })
  proxyProduct(@Param("id") id: string) {
    // Don't need code here, Nestro will auto handle proxy
  }
}

For communication between multiple services. We using communication template to get best instance for communicate

import { CommunicateRequest, CommunicationTemplate, DiscoveryService } from "@duongtrungnguyen/nestro";
import { Injectable } from "@nestjs/common";

@Injectable()
export class UserService extends CommunicationTemplate {
  constructor(discoveryService: DiscoveryService /* Load balancing service is global dependency*/) {
    // It is used to get the instance of the service
    super(discoveryService, "user");
  }

  @CommunicateRequest()
  async getUser(instance: Service) {
    // do something with instance
  }
}

Package Sidebar

Install

npm i @duongtrungnguyen/nestro

Weekly Downloads

226

Version

0.1.25

License

MIT

Unpacked Size

690 kB

Total Files

357

Last publish

Collaborators

  • duongtrungnguyen.it