Problem: Setting Up gRPC Microservices Takes Hours
You need to build a NestJS microservice with gRPC, but manually creating proto files, generating TypeScript types, configuring services, and wiring up controllers eats your entire morning.
You'll learn:
- How to prompt AI to generate complete gRPC boilerplate
- Proper proto file structure for production use
- NestJS-specific patterns AI often gets wrong
- Validation and testing setup from the start
Time: 12 min | Level: Intermediate
Why This Happens
gRPC requires coordinating multiple pieces: proto definitions, code generation, NestJS decorators, and type safety. Most tutorials show toy examples, not production patterns with proper error handling and validation.
Common symptoms:
- Spending 2+ hours on boilerplate before writing business logic
- Proto files that don't match TypeScript interfaces
- Missing error handling in gRPC interceptors
- No validation on incoming messages
Solution
Step 1: Prompt AI with Context
Instead of asking "create a NestJS gRPC service," provide structure:
Create a NestJS gRPC microservice with:
**Service:** User management (CRUD operations)
**Tech Stack:**
- NestJS 10.x
- @grpc/grpc-js (not grpc package)
- class-validator for DTOs
- Jest for testing
**Requirements:**
1. Proto file with proper package naming
2. Generated TypeScript interfaces
3. Service implementation with error handling
4. Controller with validation
5. Health check endpoint
6. Docker setup for local testing
**File structure:**
src/
├── proto/
│ └── user.proto
├── user/
│ ├── user.service.ts
│ ├── user.controller.ts
│ └── dto/
└── main.ts
Use realistic examples (not id=1, name="John").
Why this works: AI has context about your stack, sees the full structure, and knows to avoid deprecated packages.
Step 2: Review and Fix Common AI Mistakes
AI-generated code often has these issues:
❌ Wrong package import:
// AI often generates (deprecated)
import * as grpc from 'grpc';
// ✅ Correct for 2026
import * as grpc from '@grpc/grpc-js';
import { credentials } from '@grpc/grpc-js';
❌ Missing validation:
// AI skips validation
async createUser(data: CreateUserRequest) {
return this.userService.create(data);
}
// ✅ Add class-validator
import { IsEmail, IsNotEmpty, Length } from 'class-validator';
class CreateUserDto {
@IsNotEmpty()
@Length(2, 50)
name: string;
@IsEmail()
email: string;
}
❌ No error mapping:
// AI returns generic errors
throw new Error('User not found');
// ✅ Use gRPC status codes
import { RpcException } from '@nestjs/microservices';
import { status } from '@grpc/grpc-js';
throw new RpcException({
code: status.NOT_FOUND,
message: 'User not found'
});
Step 3: Generate Proto File with AI
Prompt for production-ready proto:
Create a user.proto file for NestJS gRPC with:
- Proper package naming (com.company.user.v1)
- Request/response messages with validation hints
- Service definition with all CRUD operations
- Comments for code generation tools
- Timestamp and error handling patterns
Follow Google's API design guide.
Expected output:
syntax = "proto3";
package com.company.user.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
// User service for managing user accounts
service UserService {
// Create a new user
rpc CreateUser(CreateUserRequest) returns (UserResponse);
// Get user by ID
rpc GetUser(GetUserRequest) returns (UserResponse);
// List users with pagination
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
// Update user details
rpc UpdateUser(UpdateUserRequest) returns (UserResponse);
// Delete user (soft delete)
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
}
message CreateUserRequest {
string name = 1; // Min 2, max 50 chars
string email = 2; // Valid email format
string role = 3; // ENUM: admin, user, guest
}
message GetUserRequest {
string id = 1; // UUID format
}
message UpdateUserRequest {
string id = 1;
optional string name = 2;
optional string email = 3;
}
message DeleteUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page = 1; // Default: 1
int32 page_size = 2; // Default: 10, max: 100
string filter = 3; // Optional search query
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
string role = 4;
google.protobuf.Timestamp created_at = 5;
google.protobuf.Timestamp updated_at = 6;
}
message ListUsersResponse {
repeated UserResponse users = 1;
int32 total = 2;
int32 page = 3;
int32 page_size = 4;
}
Step 4: Configure NestJS with Generated Code
Ask AI to create the main configuration:
Create main.ts for NestJS gRPC microservice using the user.proto file.
Include:
- Proper gRPC server options
- Proto path resolution
- Graceful shutdown
- Error logging
Generated main.ts:
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
package: 'com.company.user.v1',
protoPath: join(__dirname, '../proto/user.proto'),
url: '0.0.0.0:50051',
loader: {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
},
},
},
);
// Graceful shutdown
app.enableShutdownHooks();
await app.listen();
console.log('gRPC server running on 0.0.0.0:50051');
}
bootstrap();
Why these options matter:
keepCase: true- Preserves proto field names (don't convert to camelCase)longs: String- Avoids BigInt issues in JavaScriptenableShutdownHooks()- Properly closes connections on SIGTERM
Step 5: Add Validation Layer
AI rarely generates proper validation. Prompt specifically:
Create a NestJS pipe for validating gRPC requests using class-validator.
Handle these cases:
- Transform proto messages to DTOs
- Validate with class-validator decorators
- Return gRPC status codes on validation errors
- Log validation failures
Generated validation pipe:
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
import { RpcException } from '@nestjs/microservices';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
import { status } from '@grpc/grpc-js';
@Injectable()
export class GrpcValidationPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
if (!metadata.metatype || !this.toValidate(metadata.metatype)) {
return value;
}
const object = plainToClass(metadata.metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
const messages = errors.map(err =>
Object.values(err.constraints || {}).join(', ')
);
throw new RpcException({
code: status.INVALID_ARGUMENT,
message: `Validation failed: ${messages.join('; ')}`,
});
}
return object;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
Use in controller:
import { Controller, UsePipes } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
@Controller()
export class UserController {
constructor(private readonly userService: UserService) {}
@GrpcMethod('UserService', 'CreateUser')
@UsePipes(new GrpcValidationPipe())
async createUser(data: CreateUserDto): Promise<UserResponse> {
// Validation happens before this executes
return this.userService.create(data);
}
}
Step 6: Generate Testing Setup
Final prompt for AI:
Create Jest test setup for the NestJS gRPC UserService including:
- Mock gRPC client
- Test for successful user creation
- Test for validation errors
- Test for NOT_FOUND error handling
Use realistic test data.
Generated test file:
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { RpcException } from '@nestjs/microservices';
import { status } from '@grpc/grpc-js';
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
const mockRepository = {
create: jest.fn(),
save: jest.fn(),
findOne: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get<Repository<User>>(getRepositoryToken(User));
});
describe('createUser', () => {
it('should create a user successfully', async () => {
const createDto = {
name: 'Sarah Chen',
email: 'sarah.chen@example.com',
role: 'user',
};
const savedUser = {
id: '550e8400-e29b-41d4-a716-446655440000',
...createDto,
createdAt: new Date(),
updatedAt: new Date(),
};
mockRepository.create.mockReturnValue(savedUser);
mockRepository.save.mockResolvedValue(savedUser);
const result = await service.create(createDto);
expect(result).toEqual(savedUser);
expect(mockRepository.create).toHaveBeenCalledWith(createDto);
expect(mockRepository.save).toHaveBeenCalled();
});
});
describe('getUser', () => {
it('should throw NOT_FOUND for non-existent user', async () => {
mockRepository.findOne.mockResolvedValue(null);
await expect(
service.findOne('550e8400-e29b-41d4-a716-446655440000')
).rejects.toThrow(
expect.objectContaining({
error: expect.objectContaining({
code: status.NOT_FOUND,
}),
})
);
});
});
});
Verification
Test the generated service:
# Generate TypeScript from proto
npm run proto:generate
# Run tests
npm test
# Start microservice
npm run start:dev
You should see:
- Proto compilation successful
- All tests passing (green output)
- Server listening on
0.0.0.0:50051
Test with grpcurl:
# Install grpcurl
brew install grpcurl # macOS
# or download from https://github.com/fullstorydev/grpcurl
# List services
grpcurl -plaintext localhost:50051 list
# Create user
grpcurl -plaintext -d '{
"name": "Sarah Chen",
"email": "sarah@example.com",
"role": "user"
}' localhost:50051 com.company.user.v1.UserService/CreateUser
Expected response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Sarah Chen",
"email": "sarah@example.com",
"role": "user",
"createdAt": "2026-02-14T10:30:00Z",
"updatedAt": "2026-02-14T10:30:00Z"
}
What You Learned
- AI generates 80% of boilerplate when prompted with structure
- Always add validation layer manually (AI skips it)
- Use
@grpc/grpc-jsnot deprecatedgrpcpackage - Proper error handling requires gRPC status codes
- Testing setup should be part of initial generation
Limitations:
- AI can't know your specific business logic
- Generated tests need real edge cases added
- Proto files may need adjustment for existing systems
Bonus: AI Prompts Cheatsheet
For proto files:
Create a proto file for [domain] with:
- Package: com.company.[domain].v1
- Services: [list operations]
- Follow Google API design guide
- Include pagination and timestamps
For NestJS services:
Create NestJS service implementing [proto service] with:
- TypeORM for PostgreSQL
- Error handling with gRPC status codes
- Transaction support
- Soft deletes
For testing:
Create Jest tests for [service] covering:
- Happy path with realistic data
- Validation errors
- gRPC error codes
- Edge cases: [list specific scenarios]
Time saved: 2-3 hours of boilerplate → 12 minutes of reviewing and adjusting.
Tested on NestJS 10.3.0, @grpc/grpc-js 1.10.0, Node.js 22.x, TypeScript 5.5