API Styles
Master REST, gRPC, GraphQL, and real-time protocols. Choose the right style for your communication needs.
TL;DR
Choose your API style based on your needs: REST for simplicity and compatibility, gRPC for performance and strong typing, GraphQL for flexible client queries, WebSockets for bidirectional real-time communication, and SSE for server-to-client streaming. Most systems use REST externally and gRPC or GraphQL internally. Real-time applications use WebSockets or SSE.
Learning Objectives
- Understand the characteristics of each API style
- Recognize trade-offs between simplicity, performance, and flexibility
- Apply the right style for your use case
- Know when to combine multiple styles
Motivating Scenario
Your mobile app needs to fetch user data, posts, and comments. With REST, that's three separate calls and three separate payloads with fields you don't need. With GraphQL, the client specifies exactly what it needs in one query. But your internal services communicate thousands of times per second, where REST's overhead matters. gRPC is better. Your real-time dashboard needs updates instantly. WebSockets or SSE work. REST wouldn't.
The API Styles Landscape
REST: Simplicity and Universality
Definition: Representational State Transfer. Resources are identified by URLs; operations use HTTP verbs (GET, POST, PUT, DELETE).
Characteristics:
- Text-based (JSON), human-readable
- Stateless
- Cacheable (GET requests)
- Widely understood
- Largest ecosystem
- Works through firewalls/proxies
When to Use:
- Public APIs (customers integrate)
- Browser-based clients
- Heterogeneous systems that must interoperate
- When simplicity matters more than performance
Trade-offs:
- Overhead (text serialization, HTTP headers)
- Over-fetching (get more data than needed)
- Under-fetching (need multiple requests)
- No strong typing (contract is loose)
- REST Example
// Over-fetching: Gets all fields, only needs id and name
GET /api/users/123
Response: {
id: 123,
name: "Alice",
email: "alice@example.com",
phone: "+1-555-1234",
address: "...",
preferences: {...}
}
// Under-fetching: Need multiple requests
GET /api/users/123 // User data
GET /api/users/123/posts // User's posts
GET /api/posts/456/comments // Post comments
// Typical REST endpoint
GET /api/posts // List posts
POST /api/posts // Create post
GET /api/posts/:id // Get one post
PUT /api/posts/:id // Update post
DELETE /api/posts/:id // Delete post
gRPC: Performance and Strong Typing
Definition: Google RPC framework. Uses Protocol Buffers for schema and serialization. Built on HTTP/2 for multiplexing.
Characteristics:
- Binary serialization (fast, compact)
- Strong typing (schema validation)
- Streaming (bidirectional)
- HTTP/2 multiplexing (efficient)
- Language-independent (polyglot)
- High throughput, low latency
When to Use:
- Internal service-to-service communication
- High-frequency APIs (thousands of calls/sec)
- Microservices architectures
- When performance matters
- Real-time streaming
Trade-offs:
- Steeper learning curve (Protocol Buffers)
- Not browser-friendly (needs gateway)
- Requires HTTP/2 proxy support
- Less human-readable
- gRPC Example
// Schema (strongly typed)
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
service UserService {
rpc GetUser (UserId) returns (User);
rpc ListUsers (Empty) returns (stream User); // Streaming!
rpc UpdateUser (User) returns (User);
}
// Client code (strongly typed)
const user = await userService.GetUser({ id: 123 });
console.log(user.name); // Compiler knows .name exists
// Streaming
const stream = userService.ListUsers({});
stream.on('data', (user) => console.log(user));
GraphQL: Flexible Client Queries
Definition: Query language for APIs. Clients specify exactly what data they need in a single request.
Characteristics:
- Client specifies data shape
- Single endpoint
- Strongly typed schema
- Introspection (self-documenting)
- No over-fetching (get exactly what you ask for)
- No under-fetching (one query, all data)
When to Use:
- Multiple client types with different needs (mobile, web, desktop)
- Public APIs (clients can optimize for their needs)
- Complex data relationships
- When flexibility is more important than simplicity
Trade-offs:
- More complex on the server (must handle arbitrary queries)
- Query planning overhead
- Potential for expensive queries (N+1 problem)
- Learning curve (query syntax)
- GraphQL Example
# Client specifies exactly what it needs
query GetUserWithPosts {
user(id: 123) {
id
name
email
posts {
id
title
createdAt
comments {
author
text
}
}
}
}
# Response matches the query shape
{
"user": {
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"posts": [
{
"id": 1,
"title": "GraphQL is great",
"createdAt": "2025-01-15",
"comments": [
{"author": "Bob", "text": "Agree!"}
}
}
}
WebSockets: Bidirectional Real-Time
Definition: TCP-like connection over HTTP. Both client and server can send messages anytime.
Characteristics:
- Persistent connection
- Low latency (no HTTP overhead per message)
- Bidirectional (client sends, server sends)
- Full-duplex (both directions simultaneously)
- Good for real-time applications
When to Use:
- Real-time dashboards
- Chat applications
- Live notifications
- Collaborative editing
- Multiplayer games
- Streaming data
Trade-offs:
- Connection management (stateful, must handle disconnections)
- Server resource usage (keeping connections open)
- Not cacheable
- Proxies may not support
- WebSocket Example
// Client
const ws = new WebSocket('ws://api.example.com/chat');
ws.onopen = () => {
ws.send(JSON.stringify({ message: 'Hello' }));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
ws.onerror = () => {
// Handle disconnection, reconnect with backoff
};
// Server
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
// Broadcast to all clients
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
});
SSE: Server-Sent Events
Definition: HTTP standard. Server pushes updates to client over a single HTTP connection.
Characteristics:
- One-way (server → client)
- Uses standard HTTP
- Automatic reconnection
- Works through proxies and firewalls
- Simpler than WebSocket
- Text-only (but can encode binary)
When to Use:
- Server notifications to client
- Event streams (updates, alerts)
- Live feeds
- Streaming data where server initiates
- When WebSocket complexity isn't needed
Trade-offs:
- One-way (can't send from client without separate HTTP)
- Text-only
- Old browser support requires polyfill
- SSE Example
// Client
const eventSource = new EventSource('/api/notifications');
eventSource.onopen = () => console.log('Connected');
eventSource.onmessage = (event) => {
const notification = JSON.parse(event.data);
console.log('Notification:', notification);
};
eventSource.onerror = () => {
// Automatic reconnection by browser
};
// Server (Node.js/Express)
app.get('/api/notifications', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// Send initial comment (keeps connection alive)
res.write(': connected\n\n');
// Send events
const interval = setInterval(() => {
res.write(`data: ${JSON.stringify({ message: 'Update' })}\n\n`);
}, 5000);
req.on('close', () => clearInterval(interval));
});
Comparison Matrix
| Feature | REST | gRPC | GraphQL | WebSocket | SSE |
|---|---|---|---|---|---|
| Simplicity | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Performance | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Real-time | ❌ | ✅ | ❌ | ✅ | ✅ |
| Browser-friendly | ✅ | ⭐ (gateway) | ✅ | ✅ | ✅ |
| Caching | ✅ | ❌ | Partial | ❌ | ❌ |
| Streaming | ❌ | ✅ | ❌ | ✅ | ✅ |
| Flexibility | Low | Low | High | Medium | Low |
| Type Safety | Loose | ✅ Strong | ✅ Strong | None | None |
Hybrid Approaches
Most systems use multiple styles:
- REST for public APIs (simple, universal)
- GraphQL for mobile apps (flexible, efficient)
- WebSocket for real-time dashboards (live updates)
- gRPC for high-frequency calls (fast)
- REST for human-friendly debugging
- Messaging for event-driven patterns
Self-Check
For each scenario, which API style?
- Customer-facing public API?
- Real-time stock ticker?
- Internal payment service (1M calls/day)?
- Mobile app with offline support?
- Collaborative document editor?
REST is the default. Use gRPC internally for performance. Use GraphQL when flexibility matters. Use WebSockets or SSE only for true real-time needs.
Next Steps
- Sync vs Async: Read Synchronous vs Asynchronous Communication
- Async Patterns: Learn Messaging
- Infrastructure: Explore API Gateway
References
- Fielding, R. T. (2000). "Architectural Styles and the Design of Network-based Software Architectures". PhD Thesis.
- Google. (n.d.). "gRPC: A high-performance, open-source universal RPC framework". grpc.io.
- GraphQL Foundation. (n.d.). "GraphQL: A query language for APIs". graphql.org.
- Mozilla. (2024). "WebSocket API". Developer.mozilla.org.