Skip to content
SP StackPractices
intermediate Por Mathias Paulenko

Servicios gRPC con Protocol Buffers en TypeScript

Construye servicios de alto rendimiento y fuertemente tipados usando gRPC con Protocol Buffers, cubriendo llamadas unarias, server streaming, client streaming y bidirectional streaming

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Servicios gRPC con Protocol Buffers en TypeScript

Construye APIs de alto rendimiento e independientes de lenguaje usando gRPC con Protocol Buffers. Esta recipe cubre definiciones de servicios en protobuf, generacion de codigo con TypeScript, llamadas unarias, patrones de streaming, interceptors para cross-cutting concerns y health checking para servicios de produccion.

Cuando Usar Esto

  • Comunicacion de baja latencia y alto throughput entre microservicios internos
  • Necesitas contratos fuertemente tipados con generacion de codigo automatica
  • Datos streaming (logs, eventos, file uploads) deben manejarse eficientemente

Solucion

1. Definicion de Protocol Buffer

// proto/user.proto
syntax = "proto3";

package users;

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (stream User);
  rpc CreateUsers (stream CreateUserRequest) returns (UserList);
  rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}

message GetUserRequest {
  string id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
}

message ListUsersRequest {
  int32 page = 1;
  int32 page_size = 2;
}

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

message UserList {
  repeated User users = 1;
}

message ChatMessage {
  string user_id = 1;
  string content = 2;
  int64 timestamp = 3;
}

2. Generacion de Codigo

# script de package.json
"proto:generate": "grpc_tools_node_protoc \
  --js_out=import_style=commonjs,binary:./generated \
  --grpc_out=grpc_js:./generated \
  --ts_out=grpc_js:./generated \
  --proto_path=./proto \
  ./proto/*.proto"

3. Implementacion de Servidor

// grpc/server.ts
import * as grpc from '@grpc/grpc-js';
import { UserServiceService, IUserServiceServer } from './generated/user_grpc_pb';
import { GetUserRequest, User, ListUsersRequest, CreateUserRequest, UserList, ChatMessage } from './generated/user_pb';

const server = new grpc.Server();

const userService: IUserServiceServer = {
  getUser: (call, callback) => {
    const user = new User();
    user.setId(call.request.getId());
    user.setName('Alice');
    user.setEmail('alice@example.com');
    callback(null, user);
  },

  listUsers: (call) => {
    for (let i = 1; i <= 3; i++) {
      const user = new User();
      user.setId(String(i));
      user.setName(`User ${i}`);
      call.write(user);
    }
    call.end();
  },

  createUsers: (call, callback) => {
    const users: User[] = [];
    call.on('data', (req: CreateUserRequest) => {
      const user = new User();
      user.setId(String(users.length + 1));
      user.setName(req.getName());
      users.push(user);
    });
    call.on('end', () => {
      const list = new UserList();
      list.setUsersList(users);
      callback(null, list);
    });
  },

  chat: (call) => {
    call.on('data', (msg: ChatMessage) => {
      const reply = new ChatMessage();
      reply.setUserId('server');
      reply.setContent(`Echo: ${msg.getContent()}`);
      reply.setTimestamp(Date.now());
      call.write(reply);
    });
    call.on('end', () => call.end());
  },
};

server.addService(UserServiceService, userService);
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
  server.start();
  console.log('gRPC server running on port 50051');
});

4. Implementacion de Cliente

// grpc/client.ts
import { UserServiceClient } from './generated/user_grpc_pb';

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

// Llamada unaria
function getUser(id: string): Promise<User> {
  const request = new GetUserRequest();
  request.setId(id);

  return new Promise((resolve, reject) => {
    client.getUser(request, (err, response) => {
      if (err) reject(err);
      else resolve(response!);
    });
  });
}

// Server streaming
function listUsers(): Promise<User[]> {
  return new Promise((resolve) => {
    const users: User[] = [];
    const stream = client.listUsers(new ListUsersRequest());
    stream.on('data', (user: User) => users.push(user));
    stream.on('end', () => resolve(users));
  });
}

5. Interceptor para Metadata y Deadlines

// grpc/interceptor.ts
function authInterceptor(options: grpc.InterceptorOptions, nextCall: grpc.NextCall): grpc.InterceptingCall {
  const requester = new grpc.RequesterBuilder()
    .withStart((metadata, _listener, next) => {
      metadata.add('authorization', 'Bearer token123');
      next(metadata, _listener);
    })
    .build();

  return new grpc.InterceptingCall(nextCall(options), requester);
}

const client = new UserServiceClient('localhost:50051', grpc.credentials.createInsecure(), {
  interceptors: [authInterceptor],
});

Como Funciona

  • Protobuf define contratos de servicios y schemas de mensajes
  • Generacion de codigo crea stubs tipados de servidor y cliente desde archivos .proto
  • Llamadas unarias envian un request y reciben un response
  • Server streaming envia un stream de responses para un unico request
  • Bidirectional streaming intercambia streams de mensajes en tiempo real
  • HTTP/2 multiplexa requests sobre una unica conexion para eficiencia

Consideraciones de Produccion

  • Usa certificados TLS para comunicacion inter-servicio en produccion
  • Implementa health checks con el gRPC Health Checking Protocol
  • Usa un service mesh (Istio, Linkerd) para load balancing y mTLS

Errores Comunes

  • Cambiar campos de protobuf sin actualizar todos los clientes de servicio
  • No manejar errores de stream y drops de conexion gracefulmente
  • Usar gRPC para APIs public-facing donde el soporte de browser es limitado

FAQ

P: En que se diferencia de REST? R: gRPC usa protobuf binario sobre HTTP/2, ofreciendo menor latencia y streaming built-in. REST usa JSON sobre HTTP/1.1 con soporte de cliente mas amplio.

P: Los browsers pueden llamar gRPC directamente? R: No. Usa gRPC-Web para clientes de browser, o provee un gateway REST via grpc-gateway para APIs publicas.