gRPC Transport
FraiseQL’s gRPC transport generates a Protobuf API from your compiled schema. The compiler produces transport-aware database views that skip JSON serialization entirely — typed columns map directly to protobuf fields with no JSON intermediate.
Requires the grpc-transport Cargo feature.
Transport-Aware View Compilation
Section titled “Transport-Aware View Compilation”The compiler generates the optimal view shape for each transport from the same SDK annotation:
| Transport | View shape | PostgreSQL work | Server work | Wire format |
|---|---|---|---|---|
| GraphQL | JSON-shaped (json_agg, row_to_json) | JSON serialization | Pass-through | JSON |
| REST | Same JSON views | JSON serialization | Unwrap + re-key | JSON |
| gRPC | Row-shaped (typed columns) | No JSON work | Column → protobuf field | Binary protobuf |
The gRPC path is faster end-to-end because:
- PostgreSQL skips
json_agg/row_to_json(not free, especially for nested objects) - The server never parses JSON — typed columns map directly to protobuf fields
- Protobuf wire format is smaller (no key names, varint integers, no string escaping)
Honest framing: gRPC performance is competitive with hand-written tonic, not faster — FraiseQL’s gRPC transport is built on tonic. The real win is zero handler code with near-native performance.
Automatic Endpoint Generation
Section titled “Automatic Endpoint Generation”gRPC endpoints require no SDK annotations. When gRPC is enabled, the compiler automatically generates a .proto service with one RPC per query and mutation. Method names follow naming conventions:
| Schema operation | Generated gRPC RPC | Rule |
|---|---|---|
get_user query | GetUser | PascalCase of query name |
posts list query | ListPosts (server-streaming) | List + PascalCase for list returns |
create_user mutation | CreateUser → MutationResponse | PascalCase of mutation name |
All types are included by default. Use include_types / exclude_types in [grpc] to filter which types (and their associated queries/mutations) are exposed.
Proto Generation
Section titled “Proto Generation”The .proto file is auto-generated from your compiled schema. Retrieve it with:
fraiseql compile --output-proto schema.protoUse the generated .proto to generate client code in any gRPC-supported language:
# Goprotoc --go_out=. --go-grpc_out=. schema.proto
# Pythonpython -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. schema.proto
# TypeScriptgrpc_tools_node_protoc --ts_out=. schema.protoType Mapping
Section titled “Type Mapping”| FraiseQL type | Protobuf type |
|---|---|
String | string |
Int | int32 |
BigInt | int64 |
Float | double |
Boolean | bool |
ID / UUID | string |
DateTime | google.protobuf.Timestamp |
JSON | google.protobuf.Struct |
Decimal | string (preserves precision) |
[T] | repeated T |
nullable T | optional T |
| Enum | enum |
| Object type | message |
Custom scalars not in this table are serialized as string.
Nullability
Section titled “Nullability”GraphQL is nullable-by-default; protobuf3 uses optional for nullable fields. The compiler translates:
GraphQL: name: String → proto: optional string name = 1;GraphQL: name: String! → proto: string name = 1;Nested Objects
Section titled “Nested Objects”Nested object fields compile to protobuf message types. The row-shaped view uses JOINs returning flat columns; the server reconstructs the message nesting during serialization.
Mutations
Section titled “Mutations”Mutations are automatically exposed as gRPC RPCs that call the underlying database function via execute_function_call(). The response is a standard MutationResponse message:
message MutationResponse { bool success = 1; optional string id = 2; // entity ID returned by the function optional string error = 3;}Given a mutation create_user, the compiler generates a CreateUserRequest message from the function arguments and a CreateUser RPC that returns MutationResponse. No additional annotation is needed.
Streaming RPCs
Section titled “Streaming RPCs”List queries are automatically exposed as server-streaming RPCs. When a query returns a list, the generated .proto uses returns (stream Entity) instead of a wrapper response — rows are fetched in batches from the database and streamed individually as gRPC frames:
// Generated for a list queryrpc ListPosts(ListPostsRequest) returns (stream Post);
// Generated for a single-item queryrpc GetUser(GetUserRequest) returns (User);The batch size is controlled by the stream_batch_size config field (default 500). Memory usage is O(batch_size) regardless of total result count.
Subscriptions also map to server-streaming RPCs. A @fraiseql.subscription decorator generates both a WebSocket subscription and a gRPC server-streaming RPC automatically — the same subscription logic powers both transports.
The server uses PostgreSQL LISTEN/NOTIFY (same as GraphQL subscriptions) to push events through the gRPC stream. Client-streaming and bidirectional streaming are not supported.
Configuration
Section titled “Configuration”[grpc]enabled = trueport = 50052 # separate port for gRPC (default 50052)reflection = true # gRPC reflection for grpcurl/gRPC UI (default true)max_message_size_bytes = 4194304 # 4 MiB (default)descriptor_path = "proto/descriptor.binpb" # compiled FileDescriptorSetstream_batch_size = 500 # rows per batch in server-streaming RPCs# include_types = ["User", "Post"] # whitelist (empty = all types)# exclude_types = ["InternalAudit"] # blacklistgRPC runs on a separate port (default 50052) from the HTTP server. This avoids HTTP/2 negotiation complexity and lets you expose gRPC only on internal networks while keeping HTTP public.
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable gRPC transport |
port | integer | 50052 | gRPC listen port |
reflection | bool | true | Enable gRPC server reflection |
max_message_size_bytes | integer | 4194304 | Maximum inbound message size (bytes) |
descriptor_path | string | "proto/descriptor.binpb" | Path to compiled FileDescriptorSet binary |
stream_batch_size | integer | 500 | Rows per batch in server-streaming RPCs |
include_types | string[] | [] | Whitelist of type names to expose (empty = all) |
exclude_types | string[] | [] | Blacklist of type names to hide |
Feature Flag
Section titled “Feature Flag”gRPC transport requires the grpc-transport Cargo feature at compile time. The default ghcr.io/fraiseql/server:latest image does not include this feature — use ghcr.io/fraiseql/server-full:latest (all features), or build from source (see GitHub issue #80):
cargo build --release --features grpc-transportClient Examples
Section titled “Client Examples”After generating client code from the .proto:
grpcurl -plaintext \ -d '{"id": "123"}' \ localhost:50052 \ UserService/GetUserconn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())if err != nil { log.Fatal(err)}client := pb.NewUserServiceClient(conn)user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})channel = grpc.insecure_channel('localhost:50052')stub = user_pb2_grpc.UserServiceStub(channel)user = stub.GetUser(user_pb2.GetUserRequest(id='123'))Design Considerations
Section titled “Design Considerations”Tonic Integration
Section titled “Tonic Integration”gRPC is built on tonic, which integrates with axum via shared Tower middleware. Auth, rate limiting, tracing, and all other middleware apply to gRPC requests unchanged.
Proto File Generation
Section titled “Proto File Generation”Generate proto files for client code generation:
fraiseql compile schema.py --output-proto ./proto/The generated .proto reflects the exact API shape from your compiled schema — no manual proto authoring needed.
gRPC Reflection
Section titled “gRPC Reflection”The reflection service is enabled by default when grpc-transport is active. Tools like grpcurl and gRPC UI can discover services without a .proto file:
# List all servicesgrpcurl -plaintext localhost:50052 list
# Describe a specific servicegrpcurl -plaintext localhost:50052 describe UserServiceAuthentication
Section titled “Authentication”gRPC auth follows standard gRPC conventions — pass credentials in metadata headers:
grpcurl -plaintext \ -H "authorization: Bearer $TOKEN" \ -d '{"id": "123"}' \ localhost:50052 \ UserService/GetUserThe same JWT, OIDC, and API key auth configured for GraphQL and REST applies to gRPC with no additional setup.