Websockets & Rest APIs
Modern applications often require real-time communication, seamless data transfer, and responsive interfaces. Two of the most common technologies that power client-server communication are WebSockets and REST APIs. While both are essential, they are designed for different use cases and offer distinct advantages and trade-offs.
WebSockets
WebSocket is a communication protocol that provides full-duplex, real-time communication channels over a single, long-lived connection between a client (such as a web browser) and a server. Unlike the traditional HTTP request-response model, WebSocket allows the server to send data to the client without being prompted. WebSocket provides full-duplex (i.e., bidirectional) communication channels to enable client-server interaction over a single, persistent Transmission Control Protocol (TCP) connection.
Architecture

Python implementation
- A simple websocket server that accepts a token and symbol for subscribing and sends a price (random) to the client every 3 seconds
# server.py
import asyncio
import websockets
import random
import json
VALID_TOKEN = "MY_TOKEN"
async def price_stream(websocket):
try:
# Receive the initial subscription message
message = await websocket.recv()
data = json.loads(message)
token = data.get("token")
symbol = data.get("symbol")
if token != VALID_TOKEN or not symbol:
await websocket.send(json.dumps({"error": "Invalid token or missing symbol"}))
await websocket.close()
return
print(f"Client subscribed to {symbol} with token {token}")
# Send random price updates every 3 seconds
while True:
price = round(random.uniform(95, 105), 2)
response = {
"symbol": symbol,
"price": price
}
await websocket.send(json.dumps(response))
await asyncio.sleep(3)
except websockets.ConnectionClosed:
print("Client disconnected.")
async def main():
async with websockets.serve(price_stream, "localhost", 8765):
print("WebSocket server running at ws://localhost:8765")
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())
- Simple python client to subscribe to and consume data from the above websocket
# client.py
import asyncio
import websockets
import json
async def subscribe():
uri = "ws://localhost:8765"
async with websockets.connect(uri) as websocket:
# Send token and symbol to server
token = "MY_TOKEN"
symbol = input("Enter symbol to subscribe: ").strip()
subscription_request = {
"token": token,
"symbol": symbol
}
await websocket.send(json.dumps(subscription_request))
# Listen for price updates
while True:
message = await websocket.recv()
data = json.loads(message)
if "error" in data:
print(f"Error from server: {data['error']}")
break
print(f"Price Update - {data['symbol']}: ${data['price']}")
if __name__ == "__main__":
asyncio.run(subscribe())
Use cases
Real-time messaging apps, online gaming, live dashboards, IoT device communication, and collaborative tools.
Performance
Low latency, efficient for high-frequency data transfer, but scaling requires managing persistent connections with sticky sessions or brokers.
Security
Uses WSS (WebSocket Secure) for encryption, requires manual authentication and careful handling to prevent spoofing and session hijacking.
Best practices
Authenticate at connection start, use structured message formats like JSON/Protobuf, handle reconnections, and implement routing by rooms/topics.
Challenges
Scaling complexity, debugging difficulty, state management overhead, and increased infrastructure requirements for high availability.
REST APIs
REST stands for REpresentational State Transfer. A REST API is a way for different systems (like a browser, mobile app, a backend server) to communicate over the internet using HTTP. It is not a protocol or a standard — it's an architectural style that defines a set of rules and principles on how to structure APIs.
Key rules being:
- Stateless: Each request from a client to a server must contain all the info needed to understand and process it. No client session is stored on the server side
- Resource-based: Everything is considered a resource — like a user, an image, a file, etc. Resources are represented by URLs (URIs).
- Uses HTTP Methods: Standard methods like GET, POST, PUT, DELETE, PATCH for interacting with resources.
- Uniform Interface: The format of requests and responses must follow consistent rules (like JSON responses).
- Representation-driven: Resources can be sent in various formats (JSON, XML), but JSON is the most common today.
- Cacheable: Responses should declare if they are cacheable to optimize network efficiency
Architecture

Python Implementation
FastAPI based python server:
# server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Dict
app = FastAPI()
# Sample in-memory database
todos: Dict[int, Dict[str, str]] = {
1: {"task": "Learn Python", "status": "Pending"},
2: {"task": "Learn APIs", "status": "Pending"}
}
# Request body model
class TodoItem(BaseModel):
task: str
status: str = "Pending"
# GET all todos
@app.get("/todos")
def get_todos():
return todos
# GET a specific todo
@app.get("/todos/{todo_id}")
def get_todo(todo_id: int):
todo = todos.get(todo_id)
if todo:
return todo
raise HTTPException(status_code=404, detail="Todo not found")
# POST a new todo
@app.post("/todos", status_code=201)
def create_todo(item: TodoItem):
new_id = max(todos.keys(), default=0) + 1
todos[new_id] = item.dict()
return {"message": "Todo created", "id": new_id}
# PUT update a todo
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, item: TodoItem):
if todo_id in todos:
todos[todo_id] = item.dict()
return {"message": "Todo updated"}
raise HTTPException(status_code=404, detail="Todo not found")
# DELETE a todo
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
if todo_id in todos:
del todos[todo_id]
return {"message": "Todo deleted"}
raise HTTPException(status_code=404, detail="Todo not found")
if __name__ == "__main__":
import uvicorn
uvicorn.run("server:app", host="127.0.0.1", port=5000, reload=True)
Client to fetch data from API:
# client.py
import requests
BASE_URL = "http://localhost:5000"
def list_todos():
response = requests.get(f"{BASE_URL}/todos")
print(response.json())
def get_todo(todo_id):
response = requests.get(f"{BASE_URL}/todos/{todo_id}")
print(response.json())
def create_todo(task):
payload = {"task": task}
response = requests.post(f"{BASE_URL}/todos", json=payload)
print(response.json())
def update_todo(todo_id, task, status):
payload = {"task": task, "status": status}
response = requests.put(f"{BASE_URL}/todos/{todo_id}", json=payload)
print(response.json())
def delete_todo(todo_id):
response = requests.delete(f"{BASE_URL}/todos/{todo_id}")
print(response.json())
if __name__ == "__main__":
print("Creating a new Todo...")
create_todo("Build a REST API!")
print("\nListing all Todos...")
list_todos()
print("\nGetting Todo with ID 1...")
get_todo(1)
print("\nUpdating Todo ID 2...")
update_todo(2, "Learn APIs better", "Completed")
print("\nDeleting Todo ID 1...")
delete_todo(1)
print("\nFinal Todo List:")
list_todos()
Use cases
CRUD operations, public APIs, traditional web applications, and services requiring simple, scalable, stateless communication.
Performance
Higher latency compared to WebSockets due to repeated handshakes and headers, but horizontally scalable and simple to cache.
Security
Uses HTTPS for secure transmission, supports OAuth, JWT token-based authentication, and benefits from the stateless security model.
Best practices
Use nouns for resources, proper HTTP methods for operations, error handling with standard codes, version APIs, and apply rate limiting and pagination.
Challenges
Less efficient for real-time updates (requires polling), overhead with repeated TCP handshakes, and stateless nature limiting persistent interaction.
Combining REST and WebSockets
In many real-world applications, REST and WebSockets complement each other. A common pattern is:
- Use REST for authentication, initial data fetching, and CRUD operations
- Use WebSockets for real-time updates and push notifications
This hybrid model allows developers to balance simplicity and performance.