A Developer's Guide to API Paradigms:
Choosing Between Request-Response and Event-Driven Architectures
APIs are the unseen heroes of the tech world, quietly powering the apps and services we rely on every day. Whether you're scrolling through social media, tracking a delivery, or making an online payment, APIs are making it all happen.
But behind the scenes, there’s more than one way to design an API. At their core, APIs fall into two major paradigms: request-response and event-driven. Each has its strengths, weaknesses, and ideal use cases. In this article, I’ll break down these paradigms and their subcategories to help you decide which one might be best for your next project.
This article was inspired by concepts from "Designing Web APIs: Building APIS Developers Love" and builds upon its foundational principles with additional real-world examples and practical implementations.
The Two Major API Paradigms
Let’s start with the basics.
1. Request-Response APIs
Think of this paradigm as a conversation. The client asks a question (a request), and the server provides an answer (a response). This synchronous, back-and-forth style is straightforward and widely used.
Here’s how it plays out across its most popular types:
REST (Representational State Transfer)
REST is the gold standard for building APIs. It’s simple, predictable, and based on standard HTTP methods like GET, POST, PUT, and DELETE. If you’ve worked with APIs before, chances are REST was your starting point.
Example: Fetching a user’s details with REST:
GET /users/123 HTTP/1.1
Host:
api.example.com
Authorization: Bearer <your_token>
Here, the client sends a GET request to retrieve the user with ID 123. The server responds with the user's details, typically in JSON format.
Response:
{
"id": 123,
"name": "John Doe",
"email": "
john.doe@example.com
"
}
When to Choose REST:
You’re building a public API that developers will integrate with.
Simplicity and ease of use are priorities.
Why It Works:
It’s familiar and well-documented.
It works beautifully with tools like caching to optimize performance.
Where It Struggles:
- REST can be clunky when clients need very specific data, leading to over-fetching or under-fetching.
GraphQL
GraphQL takes a “you decide” approach to data. Instead of the server dictating what’s sent, the client specifies exactly what it needs. This solves REST’s over-fetching/under-fetching problem.
Fetching a user’s name and email:
Query:
query {
user(id: 123) {
name
email
}
}
Instead of fetching all user details, the client specifies that it only needs the name and email. The server responds with only the requested fields.
Response:
{
"data": {
"user": {
"name": "John Doe",
"email": "
john.doe@example.com
"
}
}
}
When to Choose GraphQL:
Your API serves multiple clients with different data needs (e.g., a mobile app and a web dashboard).
Flexibility and precision are top priorities.
Why It Works:
Developers get exactly the data they need—nothing more, nothing less.
The strongly-typed schema ensures fewer errors and better tooling support.
Where It Struggles:
- Setting up a GraphQL server can be complex, and poorly written queries can lead to performance issues.
Remote Procedure Call (RPC)
RPC is the “just tell me what to do” API. Instead of dealing with resources or queries, the client calls a method on the server, and the server runs it. Frameworks like gRPC make RPC faster by using binary formats like Protobuf for communication.
Think of RPC as a direct command system. Instead of requesting data (like in REST) or specifying a query (like in GraphQL), the client simply tells the server, “Run this specific function or method for me.”
For example, let’s say you’re building a weather service. With RPC, the client might send a request like:
getTemperature("Lagos"),
and the server runs that exact function to return the temperature for New York.
What makes RPC special is how direct and efficient it is. It focuses on performing actions or calculations rather than representing resources.
Frameworks like gRPC (Google Remote Procedure Call) take RPC to the next level by using highly optimized communication formats like Protobuf (Protocol Buffers). Unlike JSON, which is human-readable but slower, Protobuf is compact and faster, making it ideal for high-performance systems.
Example: Calling an RPC method to get a user’s details:
Request:
{
"method": "GetUser",
"params": { "userId": 123 }
}
The client directly calls a function (GetUser) on the server with specific parameters (userId: 123). This bypasses the resource model used in REST.
Response:
{
"userId": 123,
"name": "John Doe",
"email": "
john.doe@example.com
"
}
With gRPC and Protobuf, this communication would use a compact, binary format under the hood for better performance.
When to Choose RPC:
You’re working with microservices that need to communicate quickly.
Real-time data streams or bidirectional communication is a must.
Why It Works:
It’s fast and efficient.
Perfect for low-latency, high-performance applications.
Where It Struggles:
- Protobuf isn’t as human-readable as JSON, which can make debugging harder.
2. Event-Driven APIs
Now, imagine a world where you don’t have to ask for updates. Instead, the server notifies you when something happens. That’s the essence of event-driven APIs: they’re asynchronous, push-based, and perfect for real-time interactions.
Here are the main types of event-driven APIs:
Webhooks
Webhooks are like the postal service of APIs. When an event occurs (say, a payment is successful), the server sends an HTTP POST request to a pre-defined URL.
Webhooks notify your application when specific events occur. For example, receiving a notification when a payment is successful.
Example: A payment system sends a webhook to your application:
POST /webhook/payment HTTP/1.1
Host: your-app.com
Content-Type: application/json
{
"event": "payment_success",
"transaction_id": "txn_456",
"amount": 5000
}
The payment system (server) sends a POST request to your application's endpoint (/webhook/payment) with details of the event (e.g., transaction ID, amount). Your application then processes this information.
Your server processes the event:
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook/payment', methods=['POST'])
def payment_webhook():
data = request.json
print(f"Payment {data['transaction_id']} was successful!")
return '', 200
When to Choose Webhooks:
- You need to notify clients about low-frequency events, like order confirmations or payment updates.
Why They Work:
They’re simple and lightweight.
They reduce the need for constant polling.
Where They Struggle:
- The client has to maintain a secure, always-accessible endpoint.
HTTP Streaming
HTTP streaming is like tuning into a live radio broadcast. Once the connection is open, the server streams data to the client in real-time.
In HTTP streaming, a connection remains open, and the server continuously sends data as events occur.
Example: Streaming real-time stock prices:
Client request:
GET /stocks/stream HTTP/1.1
Host: api.example.comThe client establishes a connection and listens for continuous updates (e.g., stock prices) from the server. The server sends each update as a new line of data.
Server response:
data: { "symbol": "AAPL", "price": 150.45 }
data: { "symbol": "AAPL", "price": 150.47 }
data: { "symbol": "AAPL", "price": 150.50 }
When to Choose HTTP Streaming:
- You’re building something like a stock ticker or a sports score app that needs constant updates.
Why It Works:
- It’s faster than polling and doesn’t require repeated requests.
Where It Struggles:
- Open connections can tax your server if not managed properly.
WebSockets
WebSockets are the all-stars of real-time communication. They provide a two-way channel, allowing both the client and server to send messages independently.
WebSockets enable two-way communication between the client and server, ideal for real-time applications like chat or gaming.
Example: Real-time chat with WebSockets:
Client:
const socket = new WebSocket('wss://
chat.example.com
');
socket.onopen = () => {
socket.send(JSON.stringify({ type: 'message', text: 'Hello, World!' }));
};
socket.onmessage = (event) => {
const message = JSON.parse(
event.data
);
console.log('New message:', message.text);
};
The client opens a WebSocket connection (wss://chat.example.com). When the server sends a message, the onmessage event handler processes it in real time.
Server (using Python’s websockets library):
import asyncio
import websockets
async def chat_server(websocket, path):
async for message in websocket:
print(f"Received: {message}")
await websocket.send("Message received!")
start_server = websockets.serve(chat_server, "
localhost
", 6789)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
When to Choose WebSockets:
You’re building a chat app, multiplayer game, or any collaborative tool.
High-frequency updates are essential.
Why They Work:
- They’re fast, efficient, and interactive.
Where They Struggle:
- Implementation can be tricky, and fallback mechanisms might be needed for unsupported clients.
How to Choose the Right Paradigm (These are totally my opinion)
So, which one should you pick? Here’s a quick cheat sheet:
If you’re building a standard API for CRUD operations: Go with REST.
If your clients need flexibility in data requests: GraphQL is a great choice.
If performance and low-latency communication are non-negotiable: Consider RPC or WebSockets.
If you want real-time updates without constant polling: Webhooks, HTTP Streaming, or WebSockets are your best bets.
Final Thoughts
API design is as much an art as it is a science. Choosing the right paradigm isn’t just about the technical specs—it’s about understanding your users’ needs and the problem you’re trying to solve. By mastering these paradigms and their subcategories, you can create APIs that are not just functional, but truly delightful to use.
References [1] Brenda Jin, Saurabh Sahni, and Amir Shevat. "Designing Web APIs: Building APIs That Developers Love." O'Reilly Media, Inc.
Additional resources and further reading on API design can be found in the book, which provides comprehensive coverage of API design principles and best practices.