Skip to content

feat(router): add connectrpc handler for graphql-to-grpc bridge#2379

Merged
asoorm merged 2 commits into
mainfrom
ahmet/eng-8277-connect-rpc-handler-final
Feb 18, 2026
Merged

feat(router): add connectrpc handler for graphql-to-grpc bridge#2379
asoorm merged 2 commits into
mainfrom
ahmet/eng-8277-connect-rpc-handler-final

Conversation

@asoorm

@asoorm asoorm commented Nov 28, 2025

Copy link
Copy Markdown
Contributor

Overview

This PR introduces a ConnectRPC handler that enables gRPC/Connect/gRPC-Web clients to interact with GraphQL APIs through protocol translation. The implementation provides a bridge between protobuf-based RPC protocols and GraphQL operations.

⚠️ Ignore changes to cli/ and protographic/ directories - these improvements have been extracted to PR #2436 for independent review and will be merged first.

Key Features

  • Multi-Protocol Support: Handles gRPC, Connect, and gRPC-Web protocols via Vanguard transcoder
  • Service Discovery: Convention-based service discovery from directory structure
  • Operation Registry: Service-scoped GraphQL operation management
  • Error Handling: Relay-inspired error classification (CRITICAL vs NON-CRITICAL)
  • Header Forwarding: Transparent header propagation to GraphQL endpoint

Architecture

File Responsibilities

  • handler.go: Core RPC request handler, GraphQL execution, error classification
  • operation_registry.go: Thread-safe registry for service-scoped GraphQL operations
  • proto_loader.go: Protobuf file parsing and service definition extraction
  • server.go: HTTP/2 server with lifecycle management (start/stop/reload)
  • service_discovery.go: Convention-based service discovery from filesystem
  • vanguard_service.go: Vanguard integration for protocol transcoding

Supporting Files

  • handler_error_test.go: Error handling and classification tests
  • handler_test.go: Core handler functionality tests
  • operation_registry_test.go: Registry thread-safety and service-scoping tests
  • proto_loader_test.go: Proto parsing tests
  • server_test.go: Server configuration tests
  • server_lifecycle_test.go: Server lifecycle and reload tests
  • service_discovery_test.go: Service discovery convention tests
  • vanguard_service_test.go: Protocol transcoding tests

Manual Testing

Prerequisites

Start the router with appropriate config

listen_addr: "localhost:3002"
graphql_path: "/graphql"

# ConnectRPC configuration
connect_rpc:
  enabled: true
  server:
    listen_addr: "localhost:5026"
  services_provider_id: "fs-services"
  graphql_endpoint: "http://localhost:3002/graphql"

# Storage providers for services directory
storage_providers:
  file_system:
    - id: "fs-services"
      path: "./pkg/connectrpc/samples/services"

The services directory should contain proto and named GraphQL operations. See ./pkg/connectrpc/samples/services as an example:

tree ./pkg/connectrpc/samples/services 
./pkg/connectrpc/samples/services
└── employee.v1
    ├── MutationUpdateEmployeeMood.graphql
    ├── QueryGetEmployeeById.graphql
    ├── QueryGetEmployeeByPets.graphql
    ├── QueryGetEmployeeWithMood.graphql
    ├── QueryGetEmployees.graphql
    ├── QueryGetEmployeesByPetsInlineFragment.graphql
    ├── QueryGetEmployeesByPetsNamedFragment.graphql
    ├── service.proto
    └── service.proto.lock.json

Testing with curl (Connect Protocol)

Client Validation Error

curl -s -X POST http://localhost:5026/employee.v1.EmployeeService/GetEmployeeById \
  -H "Content-Type: application/json" \
  -H "Connect-Protocol-Version: 1" \
  -d '{"employee_id": "a"}' | jq
{
  "code": "invalid_argument",
  "message": "invalid request: field 'employee_id': Int32 cannot represent non-integer value: a"
}

OK

curl -s -X POST http://localhost:5026/employee.v1.EmployeeService/GetEmployeeById \
  -H "Content-Type: application/json" \
  -H "Connect-Protocol-Version: 1" \
  -d '{"employee_id": 1}' | jq  
{
  "employee": {
    "id": 1,
    "tag": "",
    "details": {
      "pets": null,
      "location": {
        "key": {
          "name": "Germany"
        }
      },
      "forename": "Jens",
      "surname": "Neuse"
    }
  }
}

Testing with grpcurl (gRPC Protocol)

Note: Requires proto files as reflection protocol is not supported.

grpcurl -plaintext \
  -proto ./pkg/connectrpc/samples/services/employee.v1/service.proto \
  -d '{"employee_id": 1}' \
  localhost:5026 \
  employee.v1.EmployeeService/GetEmployeeById | jq
{
  "employee": {
    "id": 1,
    "details": {
      "forename": "Jens",
      "surname": "Neuse",
      "location": {
        "key": {
          "name": "Germany"
        }
      }
    }
  }
}

Testing with buf CLI (Connect Protocol)

buf curl --http2-prior-knowledge --protocol connect \
  --schema ./pkg/connectrpc/samples/services/employee.v1/service.proto \
  --data '{"employee_id": 1}' \
  http://localhost:5026/employee.v1.EmployeeService/GetEmployeeById | jq
{
  "employee": {
    "id": 1,
    "details": {
      "forename": "Jens",
      "surname": "Neuse",
      "location": {
        "key": {
          "name": "Germany"
        }
      }
    }
  }
}

grpc-web

buf curl --protocol grpcweb \
  --schema ./pkg/connectrpc/samples/services/employee.v1/service.proto \
  --data '{"employee_id": 1}' \
  http://localhost:5026/employee.v1.EmployeeService/GetEmployeeById

Service Discovery Convention

Services are discovered using this directory structure:

services/
├── employee.v1/
│   ├── employee.proto          # Service definition
│   └── operations/
│       ├── GetEmployee.graphql
│       └── UpdateEmployee.graphql
└── product.v1/
    ├── product.proto
    └── operations/
        └── GetProduct.graphql

Related Issues

Closes #ENG-8277

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ConnectRPC server support with gRPC, gRPC-Web, and Connect protocol transcoding.
    • Implemented automatic service discovery and GraphQL operation execution for protobuf services.
    • Added dynamic service reloading and graceful shutdown lifecycle.
  • Configuration

    • New ConnectRPC configuration options for server listen address, storage provider, and GraphQL endpoint.
    • Updated schema and validation for ConnectRPC settings.
  • Tests

    • Comprehensive test coverage for service discovery, protocol handling, error scenarios, and server lifecycle.

@coderabbitai

coderabbitai Bot commented Nov 28, 2025

Copy link