Statelessness in REST — Designing Scalable APIs Without Server-Side Session State
In this tutorial, you will learn about Statelessness in REST. We cover key concepts, practical examples, and best practices to help you master this topic.
Statelessness in REST means every request from client to server must contain all information needed to understand and process the request, with no server-side session state between requests.
What You'll Learn
- What statelessness means in the context of REST APIs
- How to design stateless authentication and authorization
- How statelessness enables horizontal scaling
Why It Matters
Statelessness is what makes the web scalable. Any server can handle any request because there is no session state to maintain. If a server goes down, requests are simply routed to another server. This is how Google, Amazon, and Netflix handle billions of requests.
Real-World Use
DodaTech's REST API is completely stateless. Each request includes a JWT token containing the user identity and permissions. Any of the 50 API server instances can handle any request. If an instance crashes during a request, the client retries and another instance completes it.
flowchart LR
A["Client 1"] --> LB["Load Balancer"]
B["Client 2"] --> LB
C["Client 3"] --> LB
LB --> S1["Server 1\n(stateless)"]
LB --> S2["Server 2\n(stateless)"]
LB --> S3["Server 3\n(stateless)"]
LB --> S4["Server N\n(stateless)"]
style LB fill:#dbeafe,stroke:#2563eb
note right of LB: Any server can handle\nany request
Stateless Authentication
import jwt
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
SECRET_KEY = "your-secret-key"
def authenticate(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if not token:
return jsonify({"error": "missing_token"}), 401
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
request.user = payload
except jwt.ExpiredSignatureError:
return jsonify({"error": "token_expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"error": "invalid_token"}), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/orders')
@authenticate
def get_orders():
# All state comes from the token in the request
user_id = request.user['sub']
page = request.args.get('page', 1)
orders = get_user_orders(user_id, page)
return jsonify(orders)
Stateless Pagination
All pagination state must be in the request:
@app.route('/api/users')
def get_users():
# Pagination state from query parameters
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 20, type=int)
sort = request.args.get('sort', 'created_at')
order = request.args.get('order', 'desc')
# Filter state from query parameters
status = request.args.get('status')
role = request.args.get('role')
users, total = query_users(
page=page,
per_page=per_page,
sort=sort,
order=order,
filters={'status': status, 'role': role}
)
return jsonify({
"data": users,
"page": page,
"per_page": per_page,
"total": total,
"_links": {
"self": {"href": f"/api/users?page={page}&per_page={per_page}"},
"next": {"href": f"/api/users?page={page+1}&per_page={per_page}"} if page * per_page < total else None,
"prev": {"href": f"/api/users?page={page-1}&per_page={per_page}"} if page > 1 else None
}
})
Common Mistakes
1. Using Server-Side Sessions
Storing session data on the server (like Flask session cookies or Java HttpSession) breaks statelessness. Use JWT tokens or client-managed state instead.
2. Assuming Users Are Authenticated Across Requests
Each request must authenticate independently. Do not assume that because a previous request was authenticated, the next one is too.
3. Storing Shopping Cart State on Server
A shopping cart is client state. Store it in localStorage, a client-side cookie, or in a database keyed by user ID (not session ID).
4. Using Sticky Sessions (Session Affinity)
Sticky sessions bind a client to a specific server, breaking statelessness. If that server goes down, the session is lost.
5. Relying on Server-Side Rate Limit Counters
Rate limit counters must be stored in a shared cache (Redis) not in local server memory, otherwise different servers have different counters.
Practice Questions
- What does statelessness mean in REST?
- How does statelessness improve scalability?
- How do you handle authentication in a stateless API?
- What is wrong with sticky sessions?
- How should you handle shopping cart state in a stateless API?
Answers
- Each request contains all information needed to process it, with no server-side session state. 2. Any server can handle any request, enabling horizontal scaling without session affinity. 3. Use self-contained tokens (JWT) that include all necessary identity and authorization data. 4. They create coupling between client and server, breaking statelessness and fault tolerance. 5. Store cart state on the client or in a database, not in server session memory.
Challenge
Build a stateless e-commerce API where: each request includes a JWT with user ID and role, all pagination and filtering state is in query parameters, order creation is fully self-contained (no multi-step checkout sessions), and Rate Limiting uses Redis (not server memory).
FAQ
Mini Project
Build a stateless REST API for a task management system: JWT-based authentication with user context in each request, query-parameter-based filtering and pagination, Redis-backed rate limiting, load-balanced across multiple instances, and a test that verifies any server can handle any request.
What's Next
- Learn about Caching strategies with Cache-Control and ETag
- Explore the layered system constraint with proxies and gateways
- Continue to code-on-demand as an optional REST constraint
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro