# gRPC Integration

> Comprehensive guide to gRPC for high-performance microservices communication with Protocol Buffers.

- Skill: `diegosouzapw/grpc-integration` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add diegosouzapw/grpc-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/diegosouzapw/grpc-integration/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: diegosouzapw (https://skillmd.com/u/diegosouzapw)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/diegosouzapw/grpc-integration

---


# gRPC Integration

## Overview

gRPC (Google Remote Procedure Call) is a high-performance, open-source universal RPC framework that uses Protocol Buffers for serialization. It's ideal for microservices communication, providing efficient binary serialization, streaming capabilities, and built-in code generation.

## gRPC Fundamentals and Protocol Buffers

### What is gRPC?

gRPC is a modern RPC framework that:
- Uses HTTP/2 for transport
- Employs Protocol Buffers for efficient serialization
- Supports multiple programming languages
- Provides built-in streaming (unary, server streaming, client streaming, bidirectional)
- Offers code generation from service definitions

### Protocol Buffers

Protocol Buffers (protobuf) is a language-agnostic binary serialization format:

```proto
syntax = "proto3";

package user;

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  repeated string roles = 4;
  google.protobuf.Timestamp created_at = 5;
}
```

**Key features:**
- Binary format (smaller than JSON)
- Strong typing
- Backward/forward compatible
- Efficient serialization/deserialization

## Service Definition (.proto Files)

### Basic Service Definition

```proto
syntax = "proto3";

package user.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service UserService {
  // Unary RPC
  rpc GetUser(GetUserRequest) returns (User);
  
  // Server streaming RPC
  rpc ListUsers(ListUsersRequest) returns (stream User);
  
  // Client streaming RPC
  rpc CreateUserBatch(stream CreateUserRequest) returns (CreateUserBatchResponse);
  
  // Bidirectional streaming RPC
  rpc UserEvents(stream UserEventRequest) returns (stream UserEventResponse);
}

message GetUserRequest {
  string id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  google.protobuf.Timestamp created_at = 4;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
}

message CreateUserBatchResponse {
  repeated string user_ids = 1;
  int32 created_count = 2;
}

message UserEventRequest {
  string event_type = 1;
  bytes payload = 2;
}

message UserEventResponse {
  bool acknowledged = 1;
  string message = 2;
}
```

### Proto File Organization

```
proto/
├── user/
│   ├── v1/
│   │   ├── user.proto
│   │   └── user_service.proto
│   └── v2/
│       └── user_service.proto
├── common/
│   ├── timestamp.proto
│   └── pagination.proto
└── google/
    └── protobuf/
        ├── timestamp.proto
        └── empty.proto
```

### Importing Common Definitions

```proto
// common/pagination.proto
syntax = "proto3";

package common;

message PageRequest {
  int32 page_size = 1;
  string page_token = 2;
}

message PageResponse {
  string next_page_token = 1;
  int32 total_size = 2;
}

// user_service.proto
syntax = "proto3";

package user.v1;

import "common/pagination.proto";

service UserService {
  rpc ListUsers(common.PageRequest) returns (ListUsersResponse);
}

message ListUsersResponse {
  repeated User users = 1;
  common.PageResponse page_info = 2;
}
```

## Unary, Server Streaming, Client Streaming, Bidirectional Streaming

### Unary RPC

Simplest pattern - client sends request, server sends response:

```javascript
// Client
const client = new UserServiceClient('localhost:50051', credentials.createInsecure());

const request = new GetUserRequest();
request.setId('123');

client.getUser(request, (error, response) => {
  if (error) {
    console.error('Error:', error);
    return;
  }
  console.log('User:', response.toObject());
});

// Server
function getUser(call, callback) {
  const user = db.getUser(call.request.getId());
  callback(null, user);
}
```

### Server Streaming RPC

Client sends request, server returns stream of responses:

```javascript
// Client
const call = client.listUsers(new ListUsersRequest());

call.on('data', (user) => {
  console.log('User:', user.toObject());
});

call.on('end', () => {
  console.log('Stream ended');
});

call.on('error', (error) => {
  console.error('Error:', error);
});

// Server
function listUsers(call) {
  const users = db.listUsers();
  users.forEach(user => {
    call.write(user);
  });
  call.end();
}
```

### Client Streaming RPC

Client sends stream of requests, server returns single response:

```javascript
// Client
const call = client.createUserBatch((error, response) => {
  if (error) {
    console.error('Error:', error);
    return;
  }
  console.log('Created:', response.toObject());
});

const users = [{ name: 'John', email: 'john@example.com' }, ...];
users.forEach(user => {
  const request = new CreateUserRequest();
  request.setName(user.name);
  request.setEmail(user.email);
  call.write(request);
});

call.end();

// Server
function createUserBatch(call, callback) {
  const userIds = [];
  
  call.on('data', (request) => {
    const userId = db.createUser(request.toObject());
    userIds.push(userId);
  });
  
  call.on('end', () => {
    const response = new CreateUserBatchResponse();
    response.setUserIdsList(userIds);
    response.setCreatedCount(userIds.length);
    callback(null, response);
  });
}
```

### Bidirectional Streaming RPC

Both client and server send streams:

```javascript
// Client
const call = client.userEvents();

call.on('data', (response) => {
  console.log('Response:', response.toObject());
});

call.on('end', () => {
  console.log('Stream ended');
});

// Send events
function sendEvent(type, payload) {
  const request = new UserEventRequest();
  request.setEventType(type);
  request.setPayload(payload);
  call.write(request);
}

// Server
function userEvents(call) {
  call.on('data', (request) => {
    const response = new UserEventResponse();
    response.setAcknowledged(true);
    response.setMessage('Event received');
    call.write(response);
  });
  
  call.on('end', () => {
    call.end();
  });
}
```

## gRPC vs REST Comparison

| Aspect | gRPC | REST |
|--------|------|------|
| **Protocol** | HTTP/2 | HTTP/1.1 or HTTP/2 |
| **Data Format** | Protocol Buffers (binary) | JSON/XML (text) |
| **Payload Size** | Smaller (binary) | Larger (text) |
| **Code Generation** | Built-in | Manual or third-party |
| **Streaming** | Native support | Limited (SSE) |
| **Browser Support** | Requires gRPC-Web | Native |
| **Learning Curve** | Steeper | Easier |
| **Tooling** | Mature | Very mature |
| **Debugging** | Requires tools | Easy (curl, browser) |
| **Use Case** | Internal microservices | Public APIs |

**When to use gRPC:**
- Internal microservices communication
- High-performance requirements
- Need for streaming
- Strong typing and code generation
- Polyglot environments

**When to use REST:**
- Public-facing APIs
- Browser-based clients
- Simple CRUD operations
- Need for easy debugging
- Legacy system integration

## Code Generation (protoc)

### Installing protoc

```bash
# macOS
brew install protobuf

# Linux
apt-get install protobuf-compiler

# Windows
# Download from https://github.com/protocolbuffers/protobuf/releases
```

### Generating Node.js Code

```bash
# Install plugins
npm install @grpc/grpc-js @grpc/proto-loader

# Generate code
protoc --js_out=import_style=commonjs,binary:. --grpc_out=. \
  --plugin=protoc-gen-grpc=$(which grpc_tools_node_protoc_plugin) \
  user_service.proto
```

### Using @grpc/proto-loader (Dynamic)

```javascript
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

const PROTO_PATH = './proto/user_service.proto';

const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const userProto = grpc.loadPackageDefinition(packageDefinition).user.v1;

const client = new userProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);
```

### Generating TypeScript Code

```bash
# Install TypeScript plugin
npm install grpc_tools_node_protoc_ts ts-protoc-gen

# Generate TypeScript code
protoc \
  --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
  --plugin=protoc-gen-grpc=./node_modules/.bin/grpc_tools_node_protoc_plugin \
  --ts_out=./generated \
  --grpc_out=./generated \
  --proto_path=./proto \
  ./proto/user_service.proto
```

### Generating Go Code

```bash
# Install Go plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Generate Go code
protoc --go_out=. --go_opt=paths=source_relative \
  --go-grpc_out=. --go-grpc_opt=paths=source_relative \
  user_service.proto
```

### Generating Python Code

```bash
# Install Python plugin
pip install grpcio grpcio-tools

# Generate Python code
python -m grpc_tools.protoc \
  -I./proto \
  --python_out=./generated \
  --grpc_python_out=./generated \
  ./proto/user_service.proto
```

## Error Handling and Status Codes

### gRPC Status Codes

```javascript
const grpc = require('@grpc/grpc-js');

// Common status codes
grpc.status.OK                    // Operation completed successfully
grpc.status.CANCELLED             // Operation was cancelled
grpc.status.UNKNOWN               // Unknown error
grpc.status.INVALID_ARGUMENT      // Client specified invalid argument
grpc.status.DEADLINE_EXCEEDED     // Deadline expired before operation could complete
grpc.status.NOT_FOUND             // Requested entity not found
grpc.status.ALREADY_EXISTS        // Entity already exists
grpc.status.PERMISSION_DENIED     // Caller lacks permission
grpc.status.UNAUTHENTICATED       // Request lacks valid authentication
grpc.status.RESOURCE_EXHAUSTED    // Resource exhausted (quota limit)
grpc.status.FAILED_PRECONDITION   // Operation rejected because system is not in correct state
grpc.status.ABORTED               // Operation was aborted
grpc.status.OUT_OF_RANGE          // Operation attempted past valid range
grpc.status.UNIMPLEMENTED         // Operation is not implemented
grpc.status.INTERNAL              // Internal error
grpc.status.UNAVAILABLE           // Service currently unavailable
grpc.status.DATA_LOSS             // Unrecoverable data loss
```

### Error Handling in Server

```javascript
const grpc = require('@grpc/grpc-js');

function getUser(call, callback) {
  try {
    const userId = call.request.getId();
    
    if (!userId) {
      const error = new Error('User ID is required');
      error.code = grpc.status.INVALID_ARGUMENT;
      error.metadata = new grpc.Metadata();
      error.metadata.set('field', 'id');
      return callback(error);
    }
    
    const user = db.getUser(userId);
    
    if (!user) {
      const error = new Error('User not found');
      error.code = grpc.status.NOT_FOUND;
      return callback(error);
    }
    
    callback(null, user);
  } catch (error) {
    console.error('Error in getUser:', error);
    const grpcError = new Error('Internal server error');
    grpcError.code = grpc.status.INTERNAL;
    callback(grpcError);
  }
}
```

### Error Handling in Client

```javascript
client.getUser(request, (error, response) => {
  if (error) {
    switch (error.code) {
      case grpc.status.NOT_FOUND:
        console.error('User not found');
        break;
      case grpc.status.PERMISSION_DENIED:
        console.error('Permission denied');
        break;
      case grpc.status.DEADLINE_EXCEEDED:
        console.error('Request timeout');
        break;
      default:
        console.error('Error:', error.message);
    }
    return;
  }
  console.log('User:', response.toObject());
});
```

### Custom Error Metadata

```javascript
// Server
function createUser(call, callback) {
  try {
    const { name, email } = call.request.toObject();
    
    const validationErrors = validateUser({ name, email });
    if (validationErrors.length > 0) {
      const error = new Error('Validation failed');
      error.code = grpc.status.INVALID_ARGUMENT;
      error.metadata = new grpc.Metadata();
      error.metadata.set('validation-errors', JSON.stringify(validationErrors));
      return callback(error);
    }
    
    const user = db.createUser({ name, email });
    callback(null, user);
  } catch (error) {
    callback(error);
  }
}

// Client
client.createUser(request, (error, response) => {
  if (error && error.code === grpc.status.INVALID_ARGUMENT) {
    const validationErrors = JSON.parse(
      error.metadata.get('validation-errors')[0]
    );
    console.error('Validation errors:', validationErrors);
  }
});
```

## Interceptors and Middleware

### Server Interceptors

```javascript
const grpc = require('@grpc/grpc-js');

// Logging interceptor
const loggingInterceptor = (options, nextCall) => {
  return new grpc.ServerInterceptingCall(nextCall(options), {
    start: (metadata, listener, next) => {
      console.log(`[Request] ${options.method_definition.path}`);
      const newListener = {
        onReceiveMessage: (message, next) => {
          console.log(`[Message] ${JSON.stringify(message)}`);
          next(message);
        },
        onReceiveStatus: (status, next) => {
          console.log(`[Status] ${status.code} - ${status.details}`);
          next(status);
        },
      };
      next(metadata, newListener);
    },
  });
};

// Authentication interceptor
const authInterceptor = (options, nextCall) => {
  return new grpc.ServerInterceptingCall(nextCall(options), {
    start: (metadata, listener, next) => {
      const token = metadata.get('authorization')[0];
      
      if (!token) {
        const error = {
          code: grpc.status.UNAUTHENTICATED,
          details: 'Missing authentication token',
        };
        return next(null, { status: error });
      }
      
      const user = verifyToken(token);
      if (!user) {
        const error = {
          code: grpc.status.UNAUTHENTICATED,
          details: 'Invalid token',
        };
        return next(null, { status: error });
      }
      
      // Add user to call
      options.user = user;
      next(metadata, listener);
    },
  });
};

// Apply interceptors
const server = new grpc.Server();
server.addService(userProto.UserService.service, { getUser, ... });
server.bindAsync(
  '0.0.0.0:50051',
  grpc.ServerCredentials.createInsecure(),
  () => {
    console.log('Server started');
  }
);
```

### Client Interceptors

```javascript
// Retry interceptor
const retryInterceptor = (options, nextCall) => {
  let retryCount = 0;
  const maxRetries = 3;
  
  const newCall = nextCall(options);
  const originalStart = newCall.startWithContext;
  
  newCall.startWithContext = function (metadata, listener, next) {
    const retryListener = {
      onReceiveStatus: (status, next) => {
        if (status.code === grpc.status.UNAVAILABLE && retryCount < maxRetries) {
          retryCount++;
          console.log(`Retrying... (${retryCount}/${maxRetries})`);
          setTimeout(() => {
            newCall.startWithContext(metadata, listener, next);
          }, 1000 * retryCount);
          return;
        }
        next(status);
      },
    };
    originalStart.call(this, metadata, retryListener, next);
  };
  
  return newCall;
};

// Apply client interceptors
const client = new userProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure(),
  { interceptors: [retryInterceptor] }
);
```

## Authentication (TLS, JWT, API Keys)

### TLS/SSL Configuration

```javascript
const grpc = require('@grpc/grpc-js');
const fs = require('fs');

// Server with TLS
const serverCredentials = grpc.ServerCredentials.createSsl(
  fs.readFileSync('ca.crt'), // CA certificate
  [
    {
      cert_chain: fs.readFileSync('server.crt'),
      private_key: fs.readFileSync('server.key'),
    },
  ],
  false // check client certificate
);

server.bindAsync(
  '0.0.0.0:50051',
  serverCredentials,
  () => {
    console.log('Secure server started');
  }
);

// Client with TLS
const clientCredentials = grpc.credentials.createSsl(
  fs.readFileSync('ca.crt'),
  fs.readFileSync('client.key'),
  fs.readFileSync('client.crt')
);

const client = new userProto.UserService(
  'localhost:50051',
  clientCredentials
);
```

### JWT Authentication

```javascript
const jwt = require('jsonwebtoken');

// Server JWT verification
const jwtInterceptor = (options, nextCall) => {
  return new grpc.ServerInterceptingCall(nextCall(options), {
    start: (metadata, listener, next) => {
      const token = metadata.get('authorization')?.[0]?.replace('Bearer ', '');
      
      if (!token) {
        return next(null, {
          status: { code: grpc.status.UNAUTHENTICATED, details: 'Missing token' },
        });
      }
      
      try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        options.user = decoded;
        next(metadata, listener);
      } catch (error) {
        return next(null, {
          status: { code: grpc.status.UNAUTHENTICATED, details: 'Invalid token' },
        });
      }
    },
  });
};

// Client JWT authentication
const token = jwt.sign({ userId: '123' }, process.env.JWT_SECRET);
const metadata = new grpc.Metadata();
metadata.add('authorization', `Bearer ${token}`);

client.getUser(request, metadata, (error, response) => {
  // ...
});
```

### API Key Authentication

```javascript
// Server API key verification
const apiKeyInterceptor = (options, nextCall) => {
  return new grpc.ServerInterceptingCall(nextCall(options), {
    start: (metadata, listener, next) => {
      const apiKey = metadata.get('x-api-key')?.[0];
      
      if (!apiKey || !isValidApiKey(apiKey)) {
        return next(null, {
          status: { code: grpc.status.UNAUTHENTICATED, details: 'Invalid API key' },
        });
      }
      
      options.apiKey = apiKey;
      next(metadata, listener);
    },
  });
};

function isValidApiKey(apiKey) {
  // Check against database or config
  return process.env.API_KEYS.includes(apiKey);
}

// Client API key authentication
const metadata = new grpc.Metadata();
metadata.add('x-api-key', process.env.API_KEY);

client.getUser(request, metadata, (error, response) => {
  // ...
});
```

## Load Balancing and Service Discovery

### Client-Side Load Balancing

```javascript
const { RoundRobin } = require('@grpc/grpc-js/build/src/load-balancer');

const client = new userProto.UserService(
  'dns:///user-service:50051', // DNS-based service discovery
  grpc.credentials.createInsecure(),
  {
    'grpc.load_balancing_config': [
      { round_robin: {} },
    ],
  }
);
```

### gRPC Name Resolution

```javascript
// Custom DNS resolver
const dns = require('dns');

function resolveService(serviceName, callback) {
  dns.resolveSrv(serviceName, (err, addresses) => {
    if (err) {
      return callback(err);
    }
    
    const targets = addresses.map(addr => ({
      host: addr.name,
      port: addr.port,
    }));
    
    callback(null, targets);
  });
}

// Use custom resolver
const client = new userProto.UserService(
  'custom:///user-service',
  grpc.credentials.createInsecure()
);
```

### Service Discovery with Consul

```javascript
const Consul = require('consul');

const consul = new Consul();

async function getServiceAddress(serviceName) {
  const services = await consul.catalog.service.nodes(serviceName);
  if (services.length === 0) {
    throw new Error(`Service ${serviceName} not found`);
  }
  
  const service = services[Math.floor(Math.random() * services.length)];
  return `${service.ServiceAddress}:${service.ServicePort}`;
}

async function createClient(serviceName) {
  const address = await getServiceAddress(serviceName);
  return new userProto.UserService(
    address,
    grpc.credentials.createInsecure()
  );
}
```

## gRPC-Web for Browser Clients

### Envoy Proxy Setup

```yaml
# envoy.yaml
static_resources:
  listeners:
  - name: grpc_web_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: AUTO
          stat_prefix: grpc_web
          route_config:
            name: local_route
            virtual_hosts:
            - name: grpc_web_backend
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  cluster: grpc_backend
          http_filters:
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.router
  clusters:
  - name: grpc_backend
    connect_timeout: 5s
    type: LOGICAL_DNS
    http2_protocol_options: {}
    load_assignment:
      cluster_name: grpc_backend
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: localhost
                port_value: 50051
```

### Browser Client

```javascript
import { grpc } from '@improbable-eng/grpc-web';
import { UserServiceClient } from './user_service_pb_service';
import { GetUserRequest } from './user_service_pb';

const client = new UserServiceClient('http://localhost:8080', null, null);

const request = new GetUserRequest();
request.setId('123');

client.getUser(request, {}, (err, response) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('User:', response.toObject());
});
```

## Health Checking

### Standard Health Check Protocol

```proto
syntax = "proto3";

package grpc.health.v1;

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;
  }
  ServingStatus status = 1;
}
```

### Implementing Health Check

```javascript
const grpc = require('@grpc/grpc-js');
const healthProto = grpc.loadPackageDefinition(
  protoLoader.loadSync('./proto/grpc/health/v1/health.proto')
).grpc.health.v1;

const healthStatus = {
  '': healthProto.HealthCheckResponse.ServingStatus.SERVING,
  'user-service': healthProto.HealthCheckResponse.ServingStatus.SERVING,
};

const healthImpl = {
  check: (call, callback) => {
    const service = call.request.getService();
    const status = healthStatus[service] || healthProto.HealthCheckResponse.ServingStatus.SERVICE_UNKNOWN;
    
    const response = new healthProto.HealthCheckResponse();
    response.setStatus(status);
    callback(null, response);
  },
  watch: (call) => {
    const service = call.request.getService();
    
    const interval = setInterval(() => {
      const status = healthStatus[service] || healthProto.HealthCheckResponse.ServingStatus.SERVICE_UNKNOWN;
      const response = new healthProto.HealthCheckResponse();
      response.setStatus(status);
      call.write(response);
    }, 1000);
    
    call.on('cancelled', () => {
      clearInterval(interval);
      call.end();
    });
  },
};

server.addService(healthProto.Health.service, healthImpl);
```

## Deadlines and Timeouts

### Setting Deadlines

```javascript
// Client deadline
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5); // 5 second deadline

client.getUser(request, { deadline }, (error, response) => {
  if (error && error.code === grpc.status.DEADLINE_EXCEEDED) {
    console.error('Request timed out');
  }
});

// Server handling deadline
function getUser(call, callback) {
  if (call.getDeadline().getTime() < Date.now()) {
    const error = new Error('Deadline exceeded');
    error.code = grpc.status.DEADLINE_EXCEEDED;
    return callback(error);
  }
  
  // Process request...
}
```

### Timeout Configuration

```javascript
const client = new userProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure(),
  {
    'grpc.max_receive_message_length': -1, // Unlimited message size
    'grpc.max_send_message_length': -1,
    'grpc.initial_reconnect_backoff_ms': 100,
    'grpc.max_reconnect_backoff_ms': 10000,
    'grpc.keepalive_time_ms': 60000,
    'grpc.keepalive_timeout_ms': 5000,
  }
);
```

## Node.js Implementation

### Complete Server Example

```javascript
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const { v4: uuidv4 } = require('uuid');

// Load proto file
const packageDefinition = protoLoader.loadSync('./proto/user_service.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const userProto = grpc.loadPackageDefinition(packageDefinition).user.v1;

// In-memory database
const users = new Map();

// Service implementation
const userService = {
  getUser: (call, callback) => {
    const user = users.get(call.request.getId());
    if (!user) {
      const error = new Error('User not found');
      error.code = grpc.status.NOT_FOUND;
      return callback(error);
    }
    callback(null, user);
  },
  
  createUser: (call, callback) => {
    const user = {
      id: uuidv4(),
      name: call.request.getName(),
      email: call.request.getEmail(),
      createdAt: new Date(),
    };
    users.set(user.id, user);
    callback(null, user);
  },
  
  listUsers: (call) => {
    users.forEach(user => {
      call.write(user);
    });
    call.end();
  },
};

// Create server
const server = new grpc.Server();
server.addService(userProto.UserService.service, userService);

server.bindAsync(
  '0.0.0.0:50051',
  grpc.ServerCredentials.createInsecure(),
  (error, port) => {
    if (error) {
      console.error('Failed to start server:', error);
      return;
    }
    console.log(`Server running on port ${port}`);
  }
);
```

### Complete Client Example

```javascript
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Load proto file
const packageDefinition = protoLoader.loadSync('./proto/user_service.proto', {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const userProto = grpc.loadPackageDefinition(packageDefinition).user.v1;

// Create client
const client = new userProto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
);

// Unary call
function getUser(id) {
  return new Promise((resolve, reject) => {
    const request = new userProto.GetUserRequest();
    request.setId(id);
    
    client.getUser(request, (error, response) => {
      if (error) {
        return reject(error);
      }
      resolve(response.toObject());
    });
  });
}

// Create user
function createUser(name, email) {
  return new Promise((resolve, reject) => {
    const request = new userProto.CreateUserRequest();
    request.setName(name);
    request.setEmail(email);
    
    client.createUser(request, (error, response) => {
      if (error) {
        return reject(error);
      }
      resolve(response.toObject());
    });
  });
}

// List users (server streaming)
function listUsers() {
  return new Promise((resolve, reject) => {
    const request = new userProto.ListUsersRequest();
    const users = [];
    
    const call = client.listUsers(request);
    
    call.on('data', (user) => {
      users.push(user.toObject());
    });
    
    call.on('end', () => {
      resolve(users);
    });
    
    call.on('error', reject);
  });
}

// Usage
(async () => {
  try {
    // Create user
    const user = await createUser('John Doe', 'john@example.com');
    console.log('Created user:', user);
    
    // Get user
    const found = await getUser(user.id);
    console.log('Found user:', found);
    
    // List users
    const allUsers = await listUsers();
    console.log('All users:', allUsers);
  } catch (error) {
    console.error('Error:', error);
  }
})();
```

## Python Implementation

### Server Example

```python
import grpc
from concurrent import futures
import user_service_pb2
import user_service_pb2_grpc
import uuid
from datetime import datetime

class UserService(user_service_pb2_grpc.UserServiceServicer):
    def __init__(self):
        self.users = {}
    
    def GetUser(self, request, context):
        user_id = request.id
        if user_id not in self.users:
            context.set_code(grpc.StatusCode.NOT_FOUND)
            context.set_details('User not found')
            return user_service_pb2.User()
        
        user = self.users[user_id]
        return user_service_pb2.User(
            id=user['id'],
            name=user['name'],
            email=user['email'],
            created_at=datetime_to_timestamp(user['created_at'])
        )
    
    def CreateUser(self, request, context):
        user_id = str(uuid.uuid4())
        user = {
            'id': user_id,
            'name': request.name,
            'email': request.email,
            'created_at': datetime.utcnow()
        }
        self.users[user_id] = user
        
        return user_service_pb2.User(
            id=user_id,
            name=request.name,
            email=request.email,
            created_at=datetime_to_timestamp(user['created_at'])
        )
    
    def ListUsers(self, request, context):
        for user in self.users.values():
            yield user_service_pb2.User(
                id=user['id'],
                name=user['name'],
                email=user['email'],
                created_at=datetime_to_timestamp(user['created_at'])
            )

def datetime_to_timestamp(dt):
    from google.protobuf.timestamp_pb2 import Timestamp
    timestamp = Timestamp()
    timestamp.FromDatetime(dt)
    return timestamp

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    user_service_pb2_grpc.add_UserServiceServicer_to_server(
        UserService(), server
    )
    server.add_insecure_port('[::]:50051')
    server.start()
    print('Server started on port 50051')
    server.wait_for_termination()

if __name__ == '__main__':
    serve()
```

### Client Example

```python
import grpc
import user_service_pb2
import user_service_pb2_grpc

def get_user(stub, user_id):
    request = user_service_pb2.GetUserRequest(id=user_id)
    response = stub.GetUser(request)
    return response

def create_user(stub, name, email):
    request = user_service_pb2.CreateUserRequest(name=name, email=email)
    response = stub.CreateUser(request)
    return response

def list_users(stub):
    request = user_service_pb2.ListUsersRequest()
    for user in stub.ListUsers(request):
        print(user)

def main():
    with grpc.insecure_channel('localhost:50051') as channel:
        stub = user_service_pb2_grpc.UserServiceStub(channel)
        
        # Create user
        user = create_user(stub, 'John Doe', 'john@example.com')
        print(f'Created user: {user.id}')
        
        # Get user
        found = get_user(stub, user.id)
        print(f'Found user: {found.name}')
        
        # List users
        print('All users:')
        list_users(stub)

if __name__ == '__main__':
    main()
```

## Go Implementation

### Server Example

```go
package main

import (
  "context"
  "log"
  "net"
  "time"

  "google.golang.org/grpc"
  "google.golang.org/grpc/codes"
  "google.golang.org/grpc/status"
  "google.golang.org/protobuf/types/known/timestamppb"

  pb "path/to/proto/user/v1"
)

type server struct {
  pb.UnimplementedUserServiceServer
  users map[string]*pb.User
}

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
  user, ok := s.users[req.Id]
  if !ok {
    return nil, status.Error(codes.NotFound, "User not found")
  }
  return user, nil
}

func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
  user := &pb.User{
    Id:        generateID(),
    Name:      req.Name,
    Email:     req.Email,
    CreatedAt: timestamppb.Now(),
  }
  s.users[user.Id] = user
  return user, nil
}

func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
  for _, user := range s.users {
    if err := stream.Send(user); err != nil {
      return err
    }
  }
  return nil
}

func generateID() string {
  return "user-" + time.Now().Format("20060102150405")
}

func main() {
  lis, err := net.Listen("tcp", ":50051")
  if err != nil {
    log.Fatalf("Failed to listen: %v", err)
  }

  s := grpc.NewServer()
  pb.RegisterUserServiceServer(s, &server{
    users: make(map[string]*pb.User),
  })

  log.Println("Server started on port 50051")
  if err := s.Serve(lis); err != nil {
    log.Fatalf("Failed to serve: %v", err)
  }
}
```

### Client Example

```go
package main

import (
  "context"
  "log"
  "time"

  "google.golang.org/grpc"
  "google.golang.org/grpc/credentials/insecure"

  pb "path/to/proto/user/v1"
)

func main() {
  conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
  if err != nil {
    log.Fatalf("Failed to connect: %v", err)
  }
  defer conn.Close()

  client := pb.NewUserServiceClient(conn)
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()

  // Create user
  createResp, err := client.CreateUser(ctx, &pb.CreateUserRequest{
    Name:  "John Doe",
    Email: "john@example.com",
  })
  if err != nil {
    log.Fatalf("Failed to create user: %v", err)
  }
  log.Printf("Created user: %s", createResp.Id)

  // Get user
  getResp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: createResp.Id})
  if err != nil {
    log.Fatalf("Failed to get user: %v", err)
  }
  log.Printf("Found user: %s", getResp.Name)

  // List users
  listResp, err := client.ListUsers(ctx, &pb.ListUsersRequest{})
  if err != nil {
    log.Fatalf("Failed to list users: %v", err)
  }
  for {
    user, err := listResp.Recv()
    if err != nil {
      break
    }
    log.Printf("User: %s (%s)", user.Name, user.Email)
  }
}
```

## Testing gRPC Services

### Unit Testing with Node.js

```javascript
const grpc = require('@grpc/grpc-js');
const { Server } = require('@grpc/grpc-js');
const assert = require('assert');

describe('UserService', () => {
  let server;
  let client;

  before((done) => {
    server = new Server();
    server.addService(userProto.UserService.service, userService);
    server.bindAsync(
      '0.0.0.0:0', // Use port 0 for random available port
      grpc.ServerCredentials.createInsecure(),
      (error, port) => {
        if (error) return done(error);
        client = new userProto.UserService(
          `localhost:${port}`,
          grpc.credentials.createInsecure()
        );
        done();
      }
    );
  });

  after((done) => {
    client.close();
    server.tryShutdown(done);
  });

  it('should create and get user', (done) => {
    const createRequest = new userProto.CreateUserRequest();
    createRequest.setName('Test User');
    createRequest.setEmail('test@example.com');

    client.createUser(createRequest, (error, user) => {
      assert.ifError(error);
      assert.strictEqual(user.getName(), 'Test User');

      const getRequest = new userProto.GetUserRequest();
      getRequest.setId(user.getId());

      client.getUser(getRequest, (error, foundUser) => {
        assert.ifError(error);
        assert.strictEqual(foundUser.getId(), user.getId());
        done();
      });
    });
  });
});
```

## Best Practices

1. **Schema Design**
   - Use semantic versioning for proto packages
   - Keep proto files in a separate repository for sharing
   - Use well-defined common types
   - Document services and messages with comments

2. **Error Handling**
   - Use appropriate gRPC status codes
   - Include helpful error messages
   - Use metadata for additional error context
   - Log errors server-side

3. **Performance**
   - Use connection pooling
   - Implement proper timeout/deadline handling
   - Enable compression for large payloads
   - Use streaming for large datasets

4. **Security**
   - Always use TLS in production
   - Implement proper authentication
   - Use mutual TLS for service-to-service communication
   - Validate all inputs

5. **Observability**
   - Implement structured logging
   - Use metrics for monitoring
   - Enable tracing with OpenTelemetry
   - Set up health checks

## Production Checklist

- [ ] Define and enforce deadlines/timeouts for every RPC (client and server).
- [ ] Standardize status codes + error details, and avoid leaking internals in messages.
- [ ] Use TLS everywhere; prefer mTLS for service-to-service traffic.
- [ ] Add observability: metrics, structured logs, and distributed tracing with correlation IDs.
- [ ] Add health checks and graceful shutdown; verify under load and deploy conditions.

## Related Skills

- `09-microservices/service-design`
- `09-microservices/service-discovery`

