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

Floating Image

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
Copy
# 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
Copy
# 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

Floating Image

Python Implementation

FastAPI based python server:

Copy
# 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:

Copy
# 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.