@nam088/nestjs-grpc
v1.1.0
Published
A modern gRPC client library for NestJS with full streaming support and smart retry strategies
Maintainers
Readme
NestJS gRPC Client
A modern, production-ready gRPC client library for NestJS with full streaming support, smart retry strategies, and comprehensive TypeScript types.
Quick Start
npm install @nam088/nestjs-grpc// app.module.ts
import { GrpcClientModule } from '@nam088/nestjs-grpc';
@Module({
imports: [
GrpcClientModule.forRoot({
services: [{
protoPath: './proto/user.proto',
package: 'user',
serviceName: 'UserService',
url: 'localhost:50051'
}],
retry: {
maxAttempts: 3,
initialBackoff: 1000,
retryableCodes: [grpc.status.UNAVAILABLE]
},
logLevel: 'DEBUG'
})
]
})
export class AppModule {}
// user.service.ts
@Injectable()
export class UserService {
constructor(private grpc: GrpcClientService) {}
getUser(id: string) {
return this.grpc.call({
package: 'user',
service: 'UserService',
method: 'GetUser',
request: { id }
});
}
}Features
Core Capabilities
- Full gRPC Support - Unary, server streaming, client streaming, bidirectional streaming
- RxJS Integration - Native Observable support with powerful operators
- TypeScript First - Complete type safety with generics and JSDoc
- Multi-Service - Manage multiple gRPC services in one module
Production Ready
- Smart Retry - Exponential/linear/fixed backoff with jitter
- Interceptors - Built-in logging, custom middleware support
- Exception Mapping - Automatic gRPC → HTTP status code conversion
- Well Tested - 46 unit tests, 87%+ code coverage
Developer Experience
- Object-Based API - Clean, readable method calls
- Comprehensive Docs - Full JSDoc with examples
- NestJS Native - Seamless integration with NestJS ecosystem
Usage Examples
Basic Call
this.grpc.call({
package: 'user',
service: 'UserService',
method: 'GetUser',
request: { id: '123' },
timeout: 5000 // Optional timeout in ms
}).subscribe(user => console.log(user));With RxJS Operators
this.grpc.call({ ... }).pipe(
timeout(5000),
map(user => user.email),
catchError(err => of(null))
).subscribe();Server Streaming
this.grpc.callServerStream({
package: 'chat',
service: 'ChatService',
method: 'StreamMessages',
request: { roomId: 'room1' }
}).subscribe(message => console.log(message));Client Streaming
const { stream, response } = this.grpc.callClientStream({
package: 'upload',
service: 'FileService',
method: 'UploadFile'
});
// Send data
stream.next({ chunk: data1 });
stream.next({ chunk: data2 });
stream.complete();
// Get response
response.subscribe(result => console.log(result));Bidirectional Streaming
const { stream, response } = this.grpc.callBidiStream({
package: 'chat',
service: 'ChatService',
method: 'Chat'
});
// Send and receive simultaneously
stream.next({ text: 'Hello' });
response.subscribe(msg => console.log(msg));Smart Retry
import { RetryStrategy } from '@nam088/nestjs-grpc';
this.grpc.call({ ... }).pipe(
RetryStrategy.retry({
maxAttempts: 3,
backoffStrategy: 'exponential',
retryableCodes: [grpc.status.UNAVAILABLE],
initialBackoff: 1000,
maxBackoff: 5000,
backoffMultiplier: 2,
enableJitter: true
})
).subscribe();With Interceptors
// app.module.ts
GrpcClientModule.forRoot({
services: [...],
interceptors: [LoggingInterceptor, MetricsInterceptor]
})
// Custom interceptor
@Injectable()
export class MetricsInterceptor implements GrpcInterceptor {
intercept(options, next) {
const start = Date.now();
return next().pipe(
tap(() => this.recordMetric(Date.now() - start))
);
}
}Exception Handling
@Catch(GrpcException)
export class GrpcExceptionFilter implements ExceptionFilter {
catch(exception: GrpcException, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse();
response.status(exception.getStatus()).json({
error: exception.details,
code: exception.code
});
}
}API Reference
GrpcClientModule
GrpcClientModule.forRoot({
services: GrpcServiceConfig[],
interceptors?: GrpcInterceptor[],
isGlobal?: boolean,
retry?: RetryConfig,
logLevel?: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'
})GrpcClientService
| Method | Description | Returns |
|--------|-------------|---------|
| call() | Unary call | Observable<TResponse> |
| callServerStream() | Server streaming | Observable<TResponse> |
| callClientStream() | Client streaming | { stream, response } |
| callBidiStream() | Bidirectional | { stream, response } |
RetryStrategy
RetryStrategy.retry({
maxAttempts?: number, // Default: 3
initialBackoff?: number, // Default: 1000
maxBackoff?: number, // Default: 5000
backoffStrategy?: 'exponential' | 'linear' | 'fixed',
backoffMultiplier?: number, // Default: 2
retryableCodes?: grpc.status[],
enableJitter?: boolean // Default: true
})Configuration
Basic Setup
GrpcClientModule.forRoot({
services: [{
protoPath: './proto/user.proto',
package: 'user',
serviceName: 'UserService',
url: 'localhost:50051'
}]
})Multiple Services
GrpcClientModule.forRoot({
services: [
{
protoPath: './proto/user.proto',
package: 'user',
serviceName: 'UserService',
url: 'localhost:50051'
},
{
protoPath: './proto/order.proto',
package: 'order',
serviceName: 'OrderService',
url: 'localhost:50052'
}
],
isGlobal: true
})With Credentials
import * as grpc from '@grpc/grpc-js';
GrpcClientModule.forRoot({
services: [{
protoPath: './proto/user.proto',
package: 'user',
serviceName: 'UserService',
url: 'api.example.com:443',
credentials: grpc.credentials.createSsl()
}]
})Testing
describe('UserService', () => {
let service: UserService;
let grpcClient: GrpcClientService;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [GrpcClientModule.forRoot({ services: [...] })],
providers: [UserService]
}).compile();
service = module.get<UserService>(UserService);
grpcClient = module.get<GrpcClientService>(GrpcClientService);
});
it('should get user', async () => {
const result = await lastValueFrom(service.getUser('123'));
expect(result).toBeDefined();
});
});Migration Guide
From @grpc/grpc-js
// Before
const client = new UserServiceClient('localhost:50051');
client.getUser({ id: '123' }, (err, response) => {
if (err) throw err;
console.log(response);
});
// After
this.grpc.call({
package: 'user',
service: 'UserService',
method: 'GetUser',
request: { id: '123' }
}).subscribe({
next: response => console.log(response),
error: err => console.error(err)
});Best Practices
Use Retry for Production
this.grpc.call({ ... }).pipe( RetryStrategy.retry({ maxAttempts: 3 }) )Add Timeouts
this.grpc.call({ ... }).pipe(timeout(5000))Handle Errors Gracefully
this.grpc.call({ ... }).pipe( catchError(err => { this.logger.error(err); return of(defaultValue); }) )Use Interceptors for Cross-Cutting Concerns
// Logging, metrics, tracing GrpcClientModule.forRoot({ interceptors: [LoggingInterceptor, MetricsInterceptor] })
License
MIT © 2025
