gRPC and GraphQL
gRPC
gRPC is a high-performance, open-source RPC framework. Built on HTTP/2 + Protocol Buffers. Created by Google, used internally at almost every major tech company.
Architecture
graph LR
CLIENT["gRPC Client<br/>Go / Java / Python / Node..."] -->|"HTTP/2 stream<br/>binary protobuf<br/>TLS encrypted"| SERVER["gRPC Server<br/>any language"]
subgraph Stack["Transport Stack"]
APP["Proto message (binary)"]
H2["HTTP/2 frame (multiplexed)"]
TLS2["TLS 1.3 (encrypted)"]
TCP2["TCP (reliable)"]
APP --> H2 --> TLS2 --> TCP2
end
Protocol Buffer Encoding
JSON vs Protobuf — same data:
{"userId": "123", "name": "Alice", "age": 30}
JSON: 38 bytes (text)
message User {
string user_id = 1;
string name = 2;
int32 age = 3;
}
Protobuf: ~12 bytes (binary, field tags + varints)
Why protobuf is smaller: Fields encoded as tag-value pairs (field_number << 3 | wire_type). No field names in the wire format. Varints use fewer bytes for small numbers.
Service Definition (.proto)
syntax = "proto3";
package order.v1;
// 4 RPC modes
service OrderService {
// Unary: one request, one response
rpc GetOrder(GetOrderRequest) returns (Order);
// Server streaming: one request, stream of responses
rpc ListOrders(ListOrdersRequest) returns (stream Order);
// Client streaming: stream of requests, one response
rpc BulkCreate(stream CreateOrderRequest) returns (BulkCreateResponse);
// Bidirectional streaming: both sides stream
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
message GetOrderRequest {
string order_id = 1;
}
message Order {
string id = 1;
string user_id = 2;
double amount = 3;
repeated string item_ids = 4;
google.protobuf.Timestamp created_at = 5;
}
# Generate Go code from .proto
protoc --go_out=. --go-grpc_out=. order.proto
# Generates: order.pb.go (message types) + order_grpc.pb.go (client+server interfaces)
gRPC Connection Flow
sequenceDiagram
participant C as gRPC Client
participant S as gRPC Server
C->>S: TCP connect to :50051
C->>S: TLS handshake (ALPN: h2)
C->>S: HTTP/2 SETTINGS frame (max_concurrent_streams, initial_window_size)
S->>C: HTTP/2 SETTINGS + SETTINGS ACK
Note over C,S: Each RPC call = one HTTP/2 stream
C->>S: HEADERS frame<br/>:method POST<br/>:path /order.v1.OrderService/GetOrder<br/>content-type: application/grpc<br/>grpc-timeout: 5S
C->>S: DATA frame (length-prefixed protobuf body)
S->>S: decode proto, execute handler
S->>C: HEADERS frame (HTTP/2 200)
S->>C: DATA frame (response protobuf)
S->>C: HEADERS frame (grpc-status: 0, grpc-message: "")
Note over C,S: Connection reused for next RPC (HTTP/2 multiplexing)
gRPC Status Codes
OK (0) Success
CANCELLED (1) Client cancelled the request
UNKNOWN (2) Server error (unhandled exception)
INVALID_ARGUMENT (3) Bad input (wrong type, missing field)
DEADLINE_EXCEEDED (4) Timeout
NOT_FOUND (5) Resource missing
ALREADY_EXISTS (6) Conflict
PERMISSION_DENIED (7) Authenticated but no permission
UNAUTHENTICATED (16) No auth token
RESOURCE_EXHAUSTED (8) Rate limited
UNAVAILABLE (14) Server down — safe to retry
INTERNAL (13) Server bug
gRPC vs REST Comparison
| Feature | REST/JSON | gRPC/Protobuf |
|---|---|---|
| Protocol | HTTP/1.1 or HTTP/2 | HTTP/2 only |
| Serialization | Text JSON | Binary Protobuf |
| Schema | Optional (OpenAPI) | Mandatory (.proto) |
| Code generation | Manual / Swagger | protoc (both sides) |
| Streaming | SSE or WebSocket workarounds | Native (4 modes) |
| Browser support | Native | Needs grpc-web proxy |
| Payload size | 5-10× larger | Compact |
| Debugging | curl, browser devtools | grpcurl, grpc-ui |
| Best for | Public APIs, browser clients | Internal microservices |
# Test gRPC endpoints with grpcurl
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext -d '{"order_id":"123"}' \
localhost:50051 order.v1.OrderService/GetOrder
GraphQL
GraphQL is a query language for APIs — clients specify exactly what data they need, nothing more.
Architecture
graph LR
CLIENT["Client<br/>Declares: I want user.name, user.email,<br/>user.posts[0..5].title"] -->|"POST /graphql<br/>HTTP/1.1 or HTTP/2<br/>application/json"| GQL["GraphQL Server<br/>Single endpoint /graphql"]
GQL --> R1["Resolver: User"]
GQL --> R2["Resolver: Posts"]
R1 --> DB1["Users DB"]
R2 --> DB2["Posts DB"]
GQL -->|"Exact data requested"| CLIENT
Query vs REST Comparison
REST — over-fetching and under-fetching:
# Need user name + their 5 latest post titles
GET /users/123 → returns ALL user fields (over-fetch)
GET /users/123/posts → returns ALL post fields (over-fetch)
→ 2 round trips (under-fetch of related data)
GraphQL — exactly what you need:
query {
user(id: "123") {
name
email
posts(limit: 5) {
title
publishedAt
}
}
}
Response:
{
"data": {
"user": {
"name": "Alice",
"email": "alice@example.com",
"posts": [
{"title": "Post 1", "publishedAt": "2024-01-15"},
{"title": "Post 2", "publishedAt": "2024-01-10"}
]
}
}
}
Schema Definition Language (SDL)
type User {
id: ID!
name: String!
email: String!
posts(limit: Int = 10, offset: Int = 0): [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
publishedAt: DateTime
}
type Query {
user(id: ID!): User
users(filter: UserFilter): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
userCreated: User! # real-time via WebSocket
orderStatusChanged(orderId: ID!): Order!
}
GraphQL Request Flow
sequenceDiagram
participant CLIENT2 as Client
participant GQL2 as GraphQL Server
participant AUTH2 as Auth Middleware
participant R1 as User Resolver
participant R2 as Posts Resolver
participant DB2 as Database
CLIENT2->>GQL2: POST /graphql<br/>{"query": "{ user(id:1) { name posts { title } } }"}
GQL2->>AUTH2: validate Bearer token
AUTH2-->>GQL2: claims: {userId: 42, role: user}
GQL2->>GQL2: parse + validate query against schema
GQL2->>R1: resolve User(id: 1)
R1->>DB2: SELECT * FROM users WHERE id=1
DB2-->>R1: {id:1, name:"Alice"}
R1-->>GQL2: User object
GQL2->>R2: resolve posts for User(id:1)
R2->>DB2: SELECT * FROM posts WHERE user_id=1 LIMIT 10
DB2-->>R2: [{title:"Post 1"}, ...]
R2-->>GQL2: Post array
GQL2->>CLIENT2: {"data": {"user": {"name":"Alice", "posts":[...]}}}
N+1 Problem and DataLoader
graph TD
Q["Query: users[0..9] with their posts"] --> NAIVE["Naive: 1 query for users + 10 queries for posts<br/>= 11 DB queries (N+1 problem)"]
Q --> LOADER["DataLoader: batch all post queries<br/>= 2 DB queries<br/>(SELECT * FROM posts WHERE user_id IN (1,2,...,10))"]
GraphQL vs REST vs gRPC
| REST | GraphQL | gRPC | |
|---|---|---|---|
| Query flexibility | Fixed endpoints | Client-defined queries | Fixed methods |
| Over-fetching | Common | Impossible | Impossible |
| Schema | Optional | Mandatory | Mandatory (.proto) |
| Real-time | SSE / WebSocket | Subscriptions over WS | Bidirectional streaming |
| Caching | HTTP cache headers | Complex (POST, persisted queries) | Per-method, application-level |
| Browser support | Native | Native | Needs grpc-web |
| Best for | Simple CRUD, public APIs | Complex data graphs, mobile | Internal high-performance services |