gRPC Package

The @hazeljs/grpc package provides gRPC server support for your HazelJS applications. It allows you to expose RPC methods with a decorator-based API, similar to NestJS microservices. Built on @grpc/grpc-js and @grpc/proto-loader, it supports unary RPC methods with full DI integration.

Purpose

Microservices often need efficient, type-safe communication between services. gRPC provides high-performance RPC over HTTP/2 with Protocol Buffers for serialization. Implementing gRPC servers from scratch requires managing proto loading, service registration, and handler wiring. The @hazeljs/grpc package simplifies this by providing:

  • Decorator-Based API: Use @GrpcMethod() to declare RPC handlers
  • DI Integration: Controllers are resolved from the HazelJS container
  • Proto Loading: Load .proto files at runtime with configurable options
  • Unary RPC: Support for request-response RPC methods
  • Module Pattern: Familiar GrpcModule.forRoot() configuration

Architecture

The package uses a service-based approach with decorator metadata for handler registration:

graph TD
  A["@GrpcMethod Decorator<br/>(Marks Methods as RPC Handlers)"] --> B["GrpcModule<br/>(Module Configuration)"]
  B --> C["GrpcServer<br/>(Loads Proto, Manages Server)"]
  C --> D["registerHandlersFromProviders<br/>(Wires Controllers)"]
  D --> E["gRPC Server<br/>(HTTP/2, Port 50051)"]
  A --> D
  
  style A fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff
  style B fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style C fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff
  style D fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
  style E fill:#f59e0b,stroke:#fbbf24,stroke-width:2px,color:#fff

Key Components

  1. GrpcModule: Configures the gRPC server with proto path, package name, and bind URL
  2. GrpcServer: Injectable service that loads proto files, registers handlers, and manages the gRPC server lifecycle
  3. @GrpcMethod Decorator: Declarative way to mark methods as RPC handlers
  4. registerHandlersFromProviders: Registers @GrpcMethod handlers from DI-resolved controllers

Advantages

1. High-Performance Communication

gRPC uses HTTP/2 and Protocol Buffers for efficient, low-latency service-to-service communication.

2. Decorator-Based API

Use @GrpcMethod('ServiceName', 'MethodName') to declare handlers—clean and easy to understand.

3. Full DI Integration

Controllers are resolved from the HazelJS container—inject services, repositories, and other providers.

4. Type-Safe Contracts

Define your API in .proto files; request and response types are enforced at runtime.

5. Microservice Ready

The gRPC server runs on a separate port from your HTTP server, ideal for microservice architectures.

6. Discovery Compatible

The Discovery package already supports protocol: 'grpc' for service registration.

Installation

npm install @hazeljs/grpc

Quick Start

1. Define your service in a .proto file

Create a .proto file that defines your service and messages:

syntax = "proto3";
package hero;

service HeroService {
  rpc FindOne (HeroById) returns (Hero);
}

message HeroById {
  int32 id = 1;
}

message Hero {
  int32 id = 1;
  string name = 2;
}

2. Import GrpcModule and create a controller

import { Injectable } from '@hazeljs/core';
import { GrpcMethod } from '@hazeljs/grpc';
import { join } from 'path';
import { HazelModule } from '@hazeljs/core';
import { GrpcModule } from '@hazeljs/grpc';

@Injectable()
export class HeroGrpcController {
  @GrpcMethod('HeroService', 'FindOne')
  findOne(data: { id: number }) {
    return { id: data.id, name: 'Hero' };
  }
}

@HazelModule({
  imports: [
    GrpcModule.forRoot({
      protoPath: join(__dirname, 'hero.proto'),
      package: 'hero',
      url: '0.0.0.0:50051',
    }),
  ],
  providers: [HeroGrpcController],
})
export class AppModule {}

3. Register handlers and start the gRPC server

In your bootstrap file, register handlers and start the gRPC server after the HTTP server:

import { HazelApp } from '@hazeljs/core';
import { GrpcModule, GrpcServer } from '@hazeljs/grpc';
import { Container } from '@hazeljs/core';
import { AppModule } from './app.module';
import { HeroGrpcController } from './hero.grpc-controller';

async function bootstrap() {
  const app = new HazelApp(AppModule);

  // Register gRPC handlers from controllers
  GrpcModule.registerHandlersFromProviders([HeroGrpcController]);

  // Start HTTP server
  await app.listen(3000);

  // Start gRPC server (runs on separate port)
  const grpcServer = Container.getInstance().resolve(GrpcServer);
  await grpcServer.start();
}

bootstrap();

Configuration

Configure the gRPC module via GrpcModule.forRoot():

GrpcModule.forRoot({
  protoPath: join(__dirname, 'hero.proto'),  // or ['a.proto', 'b.proto']
  package: 'hero',                            // package name from .proto
  url: '0.0.0.0:50051',                       // bind address (default: 0.0.0.0:50051)
  loader: {                                   // @grpc/proto-loader options
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  },
  isGlobal: true,                             // global module (default: true)
});

Proto Loader Options

The loader option is passed to @grpc/proto-loader. Common options:

OptionDescriptionDefault
keepCasePreserve field names (no camelCase conversion)true
longsType for int64/uint64 (String or Number)String
enumsType for enum valuesString
defaultsSet default values on output objectstrue
oneofsSet virtual oneof propertiestrue
includeDirsPaths to search for imported .proto files[]

Async Configuration

Use GrpcModule.forRootAsync() when configuration depends on other services:

GrpcModule.forRootAsync({
  useFactory: async (config: ConfigService) => ({
    protoPath: config.get('GRPC_PROTO_PATH'),
    package: config.get('GRPC_PACKAGE'),
    url: config.get('GRPC_URL'),
  }),
  inject: [ConfigService],
});

@GrpcMethod Decorator

The @GrpcMethod decorator maps class methods to gRPC service methods:

// Explicit service and method name
@GrpcMethod('HeroService', 'FindOne')
findOne(data: { id: number }) {
  return { id: data.id, name: 'Hero' };
}

// Method name defaults to the decorated method name
@GrpcMethod('HeroService')
findOne(data: { id: number }) {
  return { id: data.id, name: 'Hero' };
}

// Async handlers are supported
@GrpcMethod('HeroService', 'FindOne')
async findOne(data: { id: number }) {
  const hero = await this.heroRepository.findById(data.id);
  return hero;
}

Handler Signature

Handlers receive the request object (matching the proto message) and can return:

  • A plain object (matching the response message)
  • A Promise that resolves to the response

Errors are automatically passed to the gRPC callback.

GrpcServer API

The GrpcServer service provides:

// Configure (called automatically by GrpcModule.forRoot)
grpcServer.configure(options);

// Register handlers from a single provider instance
grpcServer.registerHandlersFromProvider(controllerInstance);

// Register handlers from provider classes (resolved from DI)
grpcServer.registerHandlersFromProviders([HeroGrpcController]);

// Start the gRPC server
await grpcServer.start();

// Shutdown gracefully
await grpcServer.close();

// Get underlying gRPC Server (advanced use)
const server = grpcServer.getServer();

Complete Example

// hero.proto
// syntax = "proto3"; package hero;
// service HeroService { rpc FindOne (HeroById) returns (Hero); }
// message HeroById { int32 id = 1; }
// message Hero { int32 id = 1; string name = 2; }

// hero.grpc-controller.ts
import { Injectable } from '@hazeljs/core';
import { GrpcMethod } from '@hazeljs/grpc';

@Injectable()
export class HeroGrpcController {
  constructor(private heroRepository: HeroRepository) {}

  @GrpcMethod('HeroService', 'FindOne')
  async findOne(data: { id: number }) {
    const hero = await this.heroRepository.findById(data.id);
    return { id: hero.id, name: hero.name };
  }
}

// app.module.ts
import { HazelModule } from '@hazeljs/core';
import { GrpcModule } from '@hazeljs/grpc';
import { join } from 'path';

@HazelModule({
  imports: [
    GrpcModule.forRoot({
      protoPath: join(__dirname, 'hero.proto'),
      package: 'hero',
      url: '0.0.0.0:50051',
    }),
  ],
  providers: [HeroGrpcController, HeroRepository],
})
export class AppModule {}

// main.ts
import { HazelApp } from '@hazeljs/core';
import { GrpcModule, GrpcServer } from '@hazeljs/grpc';
import { Container } from '@hazeljs/core';

const app = new HazelApp(AppModule);
GrpcModule.registerHandlersFromProviders([HeroGrpcController]);
await app.listen(3000);

const grpcServer = Container.getInstance().resolve(GrpcServer);
await grpcServer.start();

Best Practices

  1. Define proto files first: Start with your .proto definition; it's the contract between services.

  2. Register handlers before start: Call registerHandlersFromProviders before grpcServer.start().

  3. Use dependency injection: Inject repositories and services into your gRPC controllers—they're regular HazelJS providers.

  4. Handle errors: Throw errors or reject promises; they're passed to the gRPC callback with appropriate status codes.

  5. Separate ports: The gRPC server runs on a different port (e.g., 50051) from your HTTP server (e.g., 3000).

  6. Consider Discovery: If using microservices, register your gRPC service with Discovery using protocol: 'grpc'.

What's Next?

  • Learn about Discovery for service registration and load balancing
  • Explore WebSocket for real-time client communication
  • Check out Kafka for event streaming between services