{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design","title":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","description":"Un deep dive práctico en gRPC, Protocol Buffers y ConnectRPC para construir APIs type-safe con streaming, comparaciones honestas contra REST y tRPC, reflection, problemas complejos de load balancing, y código listo para producción.","language":"es","datePublished":"2026-01-06","dateModified":null,"contentFormat":"text/markdown","content":"API Design\n\n# gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código\n\n2026-01-067 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/grpc-protobuf-connectrpc-api-design>)\n\n## Protocol Buffers: La Fuente de Verdad de Tu API\n\nEl contrato de API reemplaza al YAML de OpenAPI y a los editores Swagger. Tu contrato de API es un archivo ` .proto `, y todo lo demás se genera de ahí.\n\n```protobuf\n// proto/blog/v1/blog.proto\nsyntax = \"proto3\";\n\npackage blog.v1;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/field_mask.proto\";\nimport \"buf/validate/validate.proto\";\n\nmessage Post {\n string id = 1;\n string title = 2 [(buf.validate.field).string.min_len = 1];\n string content = 3;\n string author_id = 4;\n repeated string tags = 5;\n PostStatus status = 6;\n google.protobuf.Timestamp created_at = 7;\n google.protobuf.Timestamp updated_at = 8;\n optional string featured_image_url = 9;\n}\n\nenum PostStatus {\n POST_STATUS_UNSPECIFIED = 0;\n POST_STATUS_DRAFT = 1;\n POST_STATUS_PUBLISHED = 2;\n POST_STATUS_ARCHIVED = 3;\n}\n\nmessage CreatePostRequest {\n string title = 1 [(buf.validate.field).string = {min_len: 1, max_len: 200}];\n string content = 2 [(buf.validate.field).string.min_len = 1];\n repeated string tags = 3 [(buf.validate.field).repeated.max_items = 10];\n optional string featured_image_url = 4;\n}\n\nmessage CreatePostResponse {\n Post post = 1;\n}\n\nmessage ListPostsRequest {\n int32 page_size = 1 [(buf.validate.field).int32 = {gte: 1, lte: 100}];\n string page_token = 2;\n optional PostStatus status_filter = 3;\n}\n\nmessage ListPostsResponse {\n repeated Post posts = 1;\n string next_page_token = 2;\n int32 total_count = 3;\n}\n\nmessage StreamPostsRequest {\n repeated string author_ids = 1;\n repeated PostStatus status_filter = 2;\n}\n\nservice BlogService {\n rpc CreatePost(CreatePostRequest) returns (CreatePostResponse);\n rpc ListPosts(ListPostsRequest) returns (ListPostsResponse);\n rpc StreamPosts(StreamPostsRequest) returns (stream Post);\n}\n```\n\nObservar en algunas cosas. El keyword ` optional ` hace trabajo real aquí, en proto3, todos los campos tienen defaults de zero-value. Un ` string ` defaultea a ` \"\" `, un ` int32 ` a ` 0 `. Sin ` optional `, no es posible saber si el cliente mandó un string vacío o no mandó el campo directamente. Para operaciones de update, esa distinción importa muchísimo.\n\nEl ` FieldMask ` en el request de update es la forma proto de hacer updates parciales. En vez del debate REST sobre semántica de PATCH, el cliente dice explícitamente \"estoy actualizando estos campos\" y el servidor lo respeta.\n\n## Generación de Código: Automatización del Contrato\n\nUsando ` buf ` (el toolchain moderno de protobuf):\n\n```yaml\n# buf.gen.yaml\nversion: v2\nplugins:\n - remote: buf.build/protocolbuffers/go\n out: gen/go\n opt: paths=source_relative\n - remote: buf.build/connectrpc/go\n out: gen/go\n opt: paths=source_relative\n - remote: buf.build/connectrpc/es\n out: gen/ts\n opt: target=ts\n - remote: buf.build/bufbuild/es\n out: gen/ts\n opt: target=ts\n```\n\n```bash\nbuf generate\n```\n\nEso genera interfaces tipadas de servidor en Go, stubs de cliente en Go, stubs de cliente en TypeScript, y helpers de validación. De un solo archivo ` .proto `. Cuando el proto cambia, regenerar muestra cada ubicación que se rompió en tiempo de compilación. Difícil lograr eso con una spec OpenAPI.\n\n## El Servidor Go: ConnectRPC en la Práctica\n\n```go\ntype BlogServer struct {\n db *Database\n}\n\nfunc (s *BlogServer) CreatePost(\n ctx context.Context,\n req *connect.Request[blogv1.CreatePostRequest],\n) (*connect.Response[blogv1.CreatePostResponse], error) {\n post := &blogv1.Post{\n Id: generateID(),\n Title: req.Msg.Title,\n Content: req.Msg.Content,\n AuthorId: getUserID(ctx),\n Tags: req.Msg.Tags,\n Status: blogv1.PostStatus_POST_STATUS_DRAFT,\n CreatedAt: timestamppb.Now(),\n }\n\n if err := s.db.Posts.Create(ctx, post); err != nil {\n return nil, connect.NewError(connect.CodeInternal, err)\n }\n\n return connect.NewResponse(&blogv1.CreatePostResponse{\n Post: post,\n }), nil\n}\n\nfunc (s *BlogServer) StreamPosts(\n ctx context.Context,\n req *connect.Request[blogv1.StreamPostsRequest],\n stream *connect.ServerStream[blogv1.Post],\n) error {\n ch, cleanup := s.db.Posts.Subscribe(ctx, req.Msg.AuthorIds)\n defer cleanup()\n\n ticker := time.NewTicker(30 * time.Second)\n defer ticker.Stop()\n\n for {\n select {\n case <-ctx.Done():\n return nil\n case post, ok := <-ch:\n if !ok {\n return nil\n }\n if err := stream.Send(post); err != nil {\n return err\n }\n case <-ticker.C:\n // Keepalive para que el LB no corte el stream\n }\n }\n}\n```\n\n[ConnectRPC](<https://connectrpc.com/>) es simplemente un handler HTTP. Se monta en el mux estándar de ` net/http `. Puede montarlo junto a tus endpoints REST, tus health checks, tu endpoint de métricas. Esa componibilidad es una ventaja importante sobre gRPC-Go crudo, que quiere adueñarse de todo el ciclo de vida del servidor.\n\n## El Cliente TypeScript: Browser + Node.js\n\n```typescript\nimport { createClient } from \"@connectrpc/connect\";\nimport { createConnectTransport } from \"@connectrpc/connect-web\";\nimport { BlogService } from \"./gen/ts/blog/v1/blog_pb\";\n\nconst transport = createConnectTransport({\n baseUrl: \"https://api.miapp.com\",\n});\n\nconst client = createClient(BlogService, transport);\n\n// Llamada unaria, completamente tipada\nasync function createPost(title: string, content: string) {\n const response = await client.createPost({\n title,\n content,\n tags: [\"engineering\"],\n });\n console.log(\"Creado:\", response.post?.id);\n return response.post;\n}\n\n// Server streaming, feed en tiempo real\nasync function watchPosts(authorIds: string[]) {\n for await (const post of client.streamPosts({ authorIds })) {\n console.log(\"Post nuevo:\", post.title);\n updateUI(post);\n }\n}\n```\n\nTodo los campos están tipados. Todos los métodos están tipados. La llamada a ` createPost ` no compila si pasás un número como ` title ` o te olvidás un campo requerido.\n\n## La Comparación Honesta: gRPC vs REST vs tRPC\n\nBasándose en experiencia de producción con los tres, la verdad sin barniz:\n\n**Type safety a través del cable**: gRPC/ConnectRPC gana. El contrato se define una vez en ` .proto ` y el código generado está garantizado de matchear. REST con codegen de OpenAPI se acerca pero las specs se desincronan. tRPC tiene excelente type safety pero solo dentro de TypeScript.\n\n**Performance**: Protobuf binario es 3-10x más pequeño que JSON para payloads típicos. En calls server-to-server de alto throughput, esto importa. Para un browser cargando una sola página, la diferencia es negligible.\n\n**Streaming**: gRPC tiene streaming de primera clase. REST requiere WebSockets o SSE, que son protocolos atornillados. tRPC soporta subscriptions vía WebSockets pero se siente como un afterthought.\n\n**Ecosistema**: REST gana abrumadoramente. Toda herramienta, todo proxy, todo sistema de monitoreo entiende HTTP/JSON. gRPC requiere infraestructura aware de HTTP/2. ConnectRPC puentea esta brecha soportando ambos protocolos.\n\n## Load Balancing: El Gotcha que Destroza Deploys\n\ngRPC usa HTTP/2, que multiplexa muchos requests sobre una sola conexión TCP. Genial para performance pero terrible para load balancing.\n\nUn load balancer L4 estándar (como [AWS](<https://aws.com/>) NLB) balancea a nivel de conexión. Si tu cliente gRPC abre una conexión al load balancer, todos los requests van al mismo backend. Tu servicio \"balanceado\" tiene todo el tráfico pegándole a un pod.\n\nSoluciones:\n\n**Load balancing L7**: Utilizar un proxy L7 que entienda frames HTTP/2, Envoy, nginx (con módulo gRPC), o LBs cloud-native como AWS ALB o el LB gRPC-aware de GCP.\n\n**Balanceo client-side**: El cliente gRPC mantiene conexiones a múltiples backends y distribuye requests. Este es el approach estándar en Kubernetes con servicios headless.\n\n**Service mesh**: Si se está en Kubernetes, Linkerd o Istio manejan el load balancing de gRPC transparentemente.\n\n## Cuándo Usar Este Stack\n\ngRPC + ConnectRPC es la opción correcta cuando:\n\n- Se construye comunicación service-to-service en un sistema polyglot\n- Se necesita streaming (feeds real-time, operaciones de larga duración)\n- La superficie de API está bien definida y no va a cambiar de forma frecuentemente\n- Quiero un solo contrato que genere clientes para web, mobile y server\n\nREST sigue siendo mejor opción cuando:\n\n- La API es pública y necesita máxima compatibilidad\n- El equipo es pequeño y no quiere aprender protobuf\n- La API es heavy en CRUD con patrones simples de request/response\n\ntRPC encaja mejor cuando:\n\n- Todo el stack es TypeScript (monorepo Next.js)\n- La velocidad de desarrollo importa más que el soporte cross-language\n- La API es interna y el equipo es pequeño\n\nEl tooling maduró lo suficiente como para que el argumento de \"gRPC es demasiado complejo\" ya no se sostenga. ` buf ` hace que el manejo de protos sea tan fácil como ` npm `. ConnectRPC hace que los clientes browser sean triviales. El payoff de type safety y code generation es real y se compone con el tiempo.\n\nComenzar con un archivo ` .proto `. Todo lo demás sigue de ahí.\n\n## Preguntas frecuentes\n\n### ¿Debería usar gRPC o REST para una API nueva?\n\nSi tus clientes son todos server-side (microservicios, herramientas internas), gRPC gana en type safety, performance y code generation. Si se necesita clientes browser, ConnectRPC te da la experiencia de desarrollo de gRPC con fallback a HTTP/JSON. Las APIs REST públicas todavía tienen sentido cuando se necesita máxima compatibilidad de ecosistema.\n\n### ¿Cómo se compara ConnectRPC con tRPC?\n\ntRPC es solo TypeScript y está fuertemente acoplado al ecosistema TS. ConnectRPC genera clientes para Go, TypeScript, Java, Swift, Kotlin y más desde un solo archivo .proto. Si se es polyglot o se necesita clientes mobile, ConnectRPC gana. Si se es todo TypeScript en un monorepo, tRPC es más simple.\n\n### ¿Y GraphQL?\n\nGraphQL resuelve un problema diferente, queries flexibles dirigidas por el cliente. gRPC resuelve RPC con contratos fuertes. Si tus clientes necesitan consultar combinaciones arbitrarias de campos entre entidades, GraphQL. Si teners operaciones bien definidas, gRPC/ConnectRPC.\n\n## Tips\n\n1. **Tip:** Siempre utilizar campos ` optional ` en proto3 en vez de confiar en defaults de zero-value. Sin ` optional `, no es posible distinguir entre 'el campo se seteó a 0' y 'el campo no se mandó', esto causa problemas fuerte en updates estilo PATCH.\n2. **Tip:** Habilitar gRPC reflection en todos los entornos que no sean producción. Herramientas como ` grpcurl ` y ` grpcui ` son inútiles sin ella, y tu equipo va a perder horas adivinando definiciones de servicio.\n3. **Tip:** ConnectRPC soporta tanto gRPC como JSON sobre HTTP, utilizar el modo JSON para clientes browser y debugging mobile, y protobuf binario para server-to-server. Misma definición de servicio, cero cambios de código.\n4. **Tip:** Cuando uses streaming RPCs, siempre implementá un ` keepalive ` ping. Los load balancers (especialmente AWS ALB) terminan silenciosamente streams HTTP/2 idle después de 60 segundos por defecto.\n\n- grpc\n- protobuf\n- connectrpc\n- api-design\n- streaming\n- rpc\n- typescript\n- golang\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#title"},{"level":2,"text":"Protocol Buffers: La Fuente de Verdad de Tu API","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#protocol-buffers-la-fuente-de-verdad-de-tu-api"},{"level":2,"text":"Generación de Código: Automatización del Contrato","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#generacin-de-cdigo-automatizacin-del-contrato"},{"level":2,"text":"El Servidor Go: ConnectRPC en la Práctica","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#el-servidor-go-connectrpc-en-la-prctica"},{"level":2,"text":"El Cliente TypeScript: Browser + Node.js","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#el-cliente-typescript-browser-nodejs"},{"level":2,"text":"La Comparación Honesta: gRPC vs REST vs tRPC","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#la-comparacin-honesta-grpc-vs-rest-vs-trpc"},{"level":2,"text":"Load Balancing: El Gotcha que Destroza Deploys","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#load-balancing-el-gotcha-que-destroza-deploys"},{"level":2,"text":"Cuándo Usar Este Stack","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#cundo-usar-este-stack"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#questions"},{"level":3,"text":"¿Debería usar gRPC o REST para una API nueva?","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-1"},{"level":3,"text":"¿Cómo se compara ConnectRPC con tRPC?","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-2"},{"level":3,"text":"¿Y GraphQL?","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#article-tips"}],"sections":[{"heading":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","level":1,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design","content":"API Design"},{"heading":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","level":1,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#title","content":"2026-01-067 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/grpc-protobuf-connectrpc-api-design>)"},{"heading":"Protocol Buffers: La Fuente de Verdad de Tu API","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#protocol-buffers-la-fuente-de-verdad-de-tu-api","content":"El contrato de API reemplaza al YAML de OpenAPI y a los editores Swagger. Tu contrato de API es un archivo ` .proto `, y todo lo demás se genera de ahí.\n\n```protobuf\n// proto/blog/v1/blog.proto\nsyntax = \"proto3\";\n\npackage blog.v1;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/field_mask.proto\";\nimport \"buf/validate/validate.proto\";\n\nmessage Post {\n string id = 1;\n string title = 2 [(buf.validate.field).string.min_len = 1];\n string content = 3;\n string author_id = 4;\n repeated string tags = 5;\n PostStatus status = 6;\n google.protobuf.Timestamp created_at = 7;\n google.protobuf.Timestamp updated_at = 8;\n optional string featured_image_url = 9;\n}\n\nenum PostStatus {\n POST_STATUS_UNSPECIFIED = 0;\n POST_STATUS_DRAFT = 1;\n POST_STATUS_PUBLISHED = 2;\n POST_STATUS_ARCHIVED = 3;\n}\n\nmessage CreatePostRequest {\n string title = 1 [(buf.validate.field).string = {min_len: 1, max_len: 200}];\n string content = 2 [(buf.validate.field).string.min_len = 1];\n repeated string tags = 3 [(buf.validate.field).repeated.max_items = 10];\n optional string featured_image_url = 4;\n}\n\nmessage CreatePostResponse {\n Post post = 1;\n}\n\nmessage ListPostsRequest {\n int32 page_size = 1 [(buf.validate.field).int32 = {gte: 1, lte: 100}];\n string page_token = 2;\n optional PostStatus status_filter = 3;\n}\n\nmessage ListPostsResponse {\n repeated Post posts = 1;\n string next_page_token = 2;\n int32 total_count = 3;\n}\n\nmessage StreamPostsRequest {\n repeated string author_ids = 1;\n repeated PostStatus status_filter = 2;\n}\n\nservice BlogService {\n rpc CreatePost(CreatePostRequest) returns (CreatePostResponse);\n rpc ListPosts(ListPostsRequest) returns (ListPostsResponse);\n rpc StreamPosts(StreamPostsRequest) returns (stream Post);\n}\n```\n\nObservar en algunas cosas. El keyword ` optional ` hace trabajo real aquí, en proto3, todos los campos tienen defaults de zero-value. Un ` string ` defaultea a ` \"\" `, un ` int32 ` a ` 0 `. Sin ` optional `, no es posible saber si el cliente mandó un string vacío o no mandó el campo directamente. Para operaciones de update, esa distinción importa muchísimo.\n\nEl ` FieldMask ` en el request de update es la forma proto de hacer updates parciales. En vez del debate REST sobre semántica de PATCH, el cliente dice explícitamente \"estoy actualizando estos campos\" y el servidor lo respeta."},{"heading":"Generación de Código: Automatización del Contrato","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#generacin-de-cdigo-automatizacin-del-contrato","content":"Usando ` buf ` (el toolchain moderno de protobuf):\n\n```yaml\n# buf.gen.yaml\nversion: v2\nplugins:\n - remote: buf.build/protocolbuffers/go\n out: gen/go\n opt: paths=source_relative\n - remote: buf.build/connectrpc/go\n out: gen/go\n opt: paths=source_relative\n - remote: buf.build/connectrpc/es\n out: gen/ts\n opt: target=ts\n - remote: buf.build/bufbuild/es\n out: gen/ts\n opt: target=ts\n```\n\n```bash\nbuf generate\n```\n\nEso genera interfaces tipadas de servidor en Go, stubs de cliente en Go, stubs de cliente en TypeScript, y helpers de validación. De un solo archivo ` .proto `. Cuando el proto cambia, regenerar muestra cada ubicación que se rompió en tiempo de compilación. Difícil lograr eso con una spec OpenAPI."},{"heading":"El Servidor Go: ConnectRPC en la Práctica","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#el-servidor-go-connectrpc-en-la-prctica","content":"```go\ntype BlogServer struct {\n db *Database\n}\n\nfunc (s *BlogServer) CreatePost(\n ctx context.Context,\n req *connect.Request[blogv1.CreatePostRequest],\n) (*connect.Response[blogv1.CreatePostResponse], error) {\n post := &blogv1.Post{\n Id: generateID(),\n Title: req.Msg.Title,\n Content: req.Msg.Content,\n AuthorId: getUserID(ctx),\n Tags: req.Msg.Tags,\n Status: blogv1.PostStatus_POST_STATUS_DRAFT,\n CreatedAt: timestamppb.Now(),\n }\n\n if err := s.db.Posts.Create(ctx, post); err != nil {\n return nil, connect.NewError(connect.CodeInternal, err)\n }\n\n return connect.NewResponse(&blogv1.CreatePostResponse{\n Post: post,\n }), nil\n}\n\nfunc (s *BlogServer) StreamPosts(\n ctx context.Context,\n req *connect.Request[blogv1.StreamPostsRequest],\n stream *connect.ServerStream[blogv1.Post],\n) error {\n ch, cleanup := s.db.Posts.Subscribe(ctx, req.Msg.AuthorIds)\n defer cleanup()\n\n ticker := time.NewTicker(30 * time.Second)\n defer ticker.Stop()\n\n for {\n select {\n case <-ctx.Done():\n return nil\n case post, ok := <-ch:\n if !ok {\n return nil\n }\n if err := stream.Send(post); err != nil {\n return err\n }\n case <-ticker.C:\n // Keepalive para que el LB no corte el stream\n }\n }\n}\n```\n\n[ConnectRPC](<https://connectrpc.com/>) es simplemente un handler HTTP. Se monta en el mux estándar de ` net/http `. Puede montarlo junto a tus endpoints REST, tus health checks, tu endpoint de métricas. Esa componibilidad es una ventaja importante sobre gRPC-Go crudo, que quiere adueñarse de todo el ciclo de vida del servidor."},{"heading":"El Cliente TypeScript: Browser + Node.js","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#el-cliente-typescript-browser-nodejs","content":"```typescript\nimport { createClient } from \"@connectrpc/connect\";\nimport { createConnectTransport } from \"@connectrpc/connect-web\";\nimport { BlogService } from \"./gen/ts/blog/v1/blog_pb\";\n\nconst transport = createConnectTransport({\n baseUrl: \"https://api.miapp.com\",\n});\n\nconst client = createClient(BlogService, transport);\n\n// Llamada unaria, completamente tipada\nasync function createPost(title: string, content: string) {\n const response = await client.createPost({\n title,\n content,\n tags: [\"engineering\"],\n });\n console.log(\"Creado:\", response.post?.id);\n return response.post;\n}\n\n// Server streaming, feed en tiempo real\nasync function watchPosts(authorIds: string[]) {\n for await (const post of client.streamPosts({ authorIds })) {\n console.log(\"Post nuevo:\", post.title);\n updateUI(post);\n }\n}\n```\n\nTodo los campos están tipados. Todos los métodos están tipados. La llamada a ` createPost ` no compila si pasás un número como ` title ` o te olvidás un campo requerido."},{"heading":"La Comparación Honesta: gRPC vs REST vs tRPC","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#la-comparacin-honesta-grpc-vs-rest-vs-trpc","content":"Basándose en experiencia de producción con los tres, la verdad sin barniz:\n\n**Type safety a través del cable**: gRPC/ConnectRPC gana. El contrato se define una vez en ` .proto ` y el código generado está garantizado de matchear. REST con codegen de OpenAPI se acerca pero las specs se desincronan. tRPC tiene excelente type safety pero solo dentro de TypeScript.\n\n**Performance**: Protobuf binario es 3-10x más pequeño que JSON para payloads típicos. En calls server-to-server de alto throughput, esto importa. Para un browser cargando una sola página, la diferencia es negligible.\n\n**Streaming**: gRPC tiene streaming de primera clase. REST requiere WebSockets o SSE, que son protocolos atornillados. tRPC soporta subscriptions vía WebSockets pero se siente como un afterthought.\n\n**Ecosistema**: REST gana abrumadoramente. Toda herramienta, todo proxy, todo sistema de monitoreo entiende HTTP/JSON. gRPC requiere infraestructura aware de HTTP/2. ConnectRPC puentea esta brecha soportando ambos protocolos."},{"heading":"Load Balancing: El Gotcha que Destroza Deploys","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#load-balancing-el-gotcha-que-destroza-deploys","content":"gRPC usa HTTP/2, que multiplexa muchos requests sobre una sola conexión TCP. Genial para performance pero terrible para load balancing.\n\nUn load balancer L4 estándar (como [AWS](<https://aws.com/>) NLB) balancea a nivel de conexión. Si tu cliente gRPC abre una conexión al load balancer, todos los requests van al mismo backend. Tu servicio \"balanceado\" tiene todo el tráfico pegándole a un pod.\n\nSoluciones:\n\n**Load balancing L7**: Utilizar un proxy L7 que entienda frames HTTP/2, Envoy, nginx (con módulo gRPC), o LBs cloud-native como AWS ALB o el LB gRPC-aware de GCP.\n\n**Balanceo client-side**: El cliente gRPC mantiene conexiones a múltiples backends y distribuye requests. Este es el approach estándar en Kubernetes con servicios headless.\n\n**Service mesh**: Si se está en Kubernetes, Linkerd o Istio manejan el load balancing de gRPC transparentemente."},{"heading":"Cuándo Usar Este Stack","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#cundo-usar-este-stack","content":"gRPC + ConnectRPC es la opción correcta cuando:\n\n- Se construye comunicación service-to-service en un sistema polyglot\n- Se necesita streaming (feeds real-time, operaciones de larga duración)\n- La superficie de API está bien definida y no va a cambiar de forma frecuentemente\n- Quiero un solo contrato que genere clientes para web, mobile y server\n\nREST sigue siendo mejor opción cuando:\n\n- La API es pública y necesita máxima compatibilidad\n- El equipo es pequeño y no quiere aprender protobuf\n- La API es heavy en CRUD con patrones simples de request/response\n\ntRPC encaja mejor cuando:\n\n- Todo el stack es TypeScript (monorepo Next.js)\n- La velocidad de desarrollo importa más que el soporte cross-language\n- La API es interna y el equipo es pequeño\n\nEl tooling maduró lo suficiente como para que el argumento de \"gRPC es demasiado complejo\" ya no se sostenga. ` buf ` hace que el manejo de protos sea tan fácil como ` npm `. ConnectRPC hace que los clientes browser sean triviales. El payoff de type safety y code generation es real y se compone con el tiempo.\n\nComenzar con un archivo ` .proto `. Todo lo demás sigue de ahí."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#questions","content":""},{"heading":"¿Debería usar gRPC o REST para una API nueva?","level":3,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-1","content":"Si tus clientes son todos server-side (microservicios, herramientas internas), gRPC gana en type safety, performance y code generation. Si se necesita clientes browser, ConnectRPC te da la experiencia de desarrollo de gRPC con fallback a HTTP/JSON. Las APIs REST públicas todavía tienen sentido cuando se necesita máxima compatibilidad de ecosistema."},{"heading":"¿Cómo se compara ConnectRPC con tRPC?","level":3,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-2","content":"tRPC es solo TypeScript y está fuertemente acoplado al ecosistema TS. ConnectRPC genera clientes para Go, TypeScript, Java, Swift, Kotlin y más desde un solo archivo .proto. Si se es polyglot o se necesita clientes mobile, ConnectRPC gana. Si se es todo TypeScript en un monorepo, tRPC es más simple."},{"heading":"¿Y GraphQL?","level":3,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#question-3","content":"GraphQL resuelve un problema diferente, queries flexibles dirigidas por el cliente. gRPC resuelve RPC con contratos fuertes. Si tus clientes necesitan consultar combinaciones arbitrarias de campos entre entidades, GraphQL. Si teners operaciones bien definidas, gRPC/ConnectRPC."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#article-tips","content":"1. **Tip:** Siempre utilizar campos ` optional ` en proto3 en vez de confiar en defaults de zero-value. Sin ` optional `, no es posible distinguir entre 'el campo se seteó a 0' y 'el campo no se mandó', esto causa problemas fuerte en updates estilo PATCH.\n2. **Tip:** Habilitar gRPC reflection en todos los entornos que no sean producción. Herramientas como ` grpcurl ` y ` grpcui ` son inútiles sin ella, y tu equipo va a perder horas adivinando definiciones de servicio.\n3. **Tip:** ConnectRPC soporta tanto gRPC como JSON sobre HTTP, utilizar el modo JSON para clientes browser y debugging mobile, y protobuf binario para server-to-server. Misma definición de servicio, cero cambios de código.\n4. **Tip:** Cuando uses streaming RPCs, siempre implementá un ` keepalive ` ping. Los load balancers (especialmente AWS ALB) terminan silenciosamente streams HTTP/2 idle después de 60 segundos por defecto.\n\n- grpc\n- protobuf\n- connectrpc\n- api-design\n- streaming\n- rpc\n- typescript\n- golang\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/grpc-protobuf-connectrpc-api-design"},{"text":"ConnectRPC","url":"https://connectrpc.com/"},{"text":"AWS","url":"https://aws.com/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design"},"headline":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","description":"Un deep dive práctico en gRPC, Protocol Buffers y ConnectRPC para construir APIs type-safe con streaming, comparaciones honestas contra REST y tRPC, reflection, problemas complejos de load balancing, y código listo para producción.","datePublished":"2026-01-06","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://statics.lucioduran.com/images/lucioduran-social-card.jpg","url":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"API Design","keywords":"grpc, protobuf, connectrpc, api-design, streaming, rpc, typescript, golang","wordCount":1194,"timeRequired":"PT7M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/grpc-protobuf-connectrpc-api-design"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"gRPC + Protobuf + ConnectRPC: APIs Type-Safe con Streaming y Generación de Código","item":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Debería usar gRPC o REST para una API nueva?","acceptedAnswer":{"@type":"Answer","text":"Si tus clientes son todos server-side (microservicios, herramientas internas), gRPC gana en type safety, performance y code generation. Si se necesita clientes browser, ConnectRPC te da la experiencia de desarrollo de gRPC con fallback a HTTP/JSON. Las APIs REST públicas todavía tienen sentido cuando se necesita máxima compatibilidad de ecosistema."}},{"@type":"Question","name":"¿Cómo se compara ConnectRPC con tRPC?","acceptedAnswer":{"@type":"Answer","text":"tRPC es solo TypeScript y está fuertemente acoplado al ecosistema TS. ConnectRPC genera clientes para Go, TypeScript, Java, Swift, Kotlin y más desde un solo archivo .proto. Si se es polyglot o se necesita clientes mobile, ConnectRPC gana. Si se es todo TypeScript en un monorepo, tRPC es más simple."}},{"@type":"Question","name":"¿Y GraphQL?","acceptedAnswer":{"@type":"Answer","text":"GraphQL resuelve un problema diferente, queries flexibles dirigidas por el cliente. gRPC resuelve RPC con contratos fuertes. Si tus clientes necesitan consultar combinaciones arbitrarias de campos entre entidades, GraphQL. Si teners operaciones bien definidas, gRPC/ConnectRPC."}}]}]}],"contentHash":"1ef480afc91294cd8706130bd243175e3b1b2534b9c2de958db2a8c7fceb911a","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/grpc-protobuf-connectrpc-api-design","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":62972,"contentBytes":11322}}
