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
.protofiles 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
- GrpcModule: Configures the gRPC server with proto path, package name, and bind URL
- GrpcServer: Injectable service that loads proto files, registers handlers, and manages the gRPC server lifecycle
- @GrpcMethod Decorator: Declarative way to mark methods as RPC handlers
- registerHandlersFromProviders: Registers
@GrpcMethodhandlers 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:
| Option | Description | Default |
|---|---|---|
keepCase | Preserve field names (no camelCase conversion) | true |
longs | Type for int64/uint64 (String or Number) | String |
enums | Type for enum values | String |
defaults | Set default values on output objects | true |
oneofs | Set virtual oneof properties | true |
includeDirs | Paths 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
Promisethat 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
-
Define proto files first: Start with your
.protodefinition; it's the contract between services. -
Register handlers before start: Call
registerHandlersFromProvidersbeforegrpcServer.start(). -
Use dependency injection: Inject repositories and services into your gRPC controllers—they're regular HazelJS providers.
-
Handle errors: Throw errors or reject promises; they're passed to the gRPC callback with appropriate status codes.
-
Separate ports: The gRPC server runs on a different port (e.g., 50051) from your HTTP server (e.g., 3000).
-
Consider Discovery: If using microservices, register your gRPC service with Discovery using
protocol: 'grpc'.