0

Stateless vs stateful — what each means for HTTP and your API

Intermediate5 min read·eng-13-014
interview

Concept

Stateless vs stateful — two fundamentally different models for how a server handles a series of requests from the same client.

Stateless: Each request is SELF-CONTAINED. The server processes it without reference to any previous requests. No stored context between requests. Every request carries everything needed: authentication, session ID, user preferences, etc.

Stateful: The server remembers context between requests. A client establishes a "session" — the server stores data about that client's state across requests. The server and client share a continuous context.

HTTP is inherently stateless: Each HTTP request is independent. The server doesn't know or care that you sent a request 2 seconds ago. Stateful behavior must be layered on top (sessions, cookies).

Stateless APIs (REST):

  • Auth: JWT token in every request (not stored on server).
  • Context: All parameters in every request.
  • Benefits: Horizontal scaling — any server can handle any request (no sticky sessions). Simpler servers. Easier to cache.
  • Drawbacks: Larger requests (must include auth every time). Token revocation is hard (can't invalidate a JWT without a denylist).

Stateful APIs:

  • Auth: Session cookie (server stores session).
  • Context: Server remembers what you were doing.
  • Benefits: Smaller requests (session ID is tiny). Easy revocation (delete session).
  • Drawbacks: Requires shared session storage for horizontal scaling (Redis). All servers need access to the session store. Sticky sessions or shared storage required.

WebSockets: Stateful by nature — a persistent connection where the server maintains state about connected clients.

"Stateless" in microservices: Each service must be stateless (no shared state between instances). State is externalized to Redis, databases, or event stores.

Code Example

php
<?php
// STATEFUL — server stores session, client sends session ID
// Traditional web app (not RESTful)
// config/session.php: 'driver' => 'redis' (shared store for all servers)

Route::post('/login', function (LoginRequest $request) {
    if (!Auth::attempt($request->only('email', 'password'))) {
        return back()->withErrors(['email' => 'Invalid credentials']);
    }
    $request->session()->regenerate();
    // Server stores: session_id → {user_id: 42, csrf_token: 'abc', cart: [...]}
    // Client gets: Set-Cookie: laravel_session=base64(session_id)
    return redirect('/dashboard');
});

Route::get('/dashboard', function () {
    $user = auth()->user(); // found via session lookup
    // Server: read session by cookie → get user_id → query DB → return user
    return view('dashboard');
});

// STATELESS — each request self-contained
// API — each request includes JWT with user ID
Route::post('/api/login', function (LoginRequest $request) {
    $user  = User::where('email', $request->email)->first();
    if (!$user || !\Hash::check($request->password, $user->password)) {
        return response()->json(['error' => 'Invalid credentials'], 401);
    }
    // No session created! Client stores the token.
    $token = \Tymon\JWTAuth\Facades\JWTAuth::fromUser($user);
    return response()->json(['token' => $token]);
});

Route::middleware('auth:api')->get('/api/profile', function (Request $request) {
    // Token decoded from Authorization header — no server session lookup
    return response()->json($request->user());
});

// STATELESS HORIZONTAL SCALING
// Server 1 handles request → no session to sync
// Server 2 handles next request → JWT validated independently
// Any server handles any request — no sticky sessions needed!

// STATEFUL HORIZONTAL SCALING PROBLEM
// Server 1 stores session in memory → client must hit Server 1 again!
// Solution: shared session store (Redis)
// config/session.php: 'driver' => 'redis'
// config/database.php: 'redis' → shared Redis cluster
// Now any server reads the session from Redis

// WebSocket — inherently stateful
// \Ratchet\WebSocket\WsServer handles persistent connections
// Each connected client has state (user ID, room, message history)
// Scaling WebSockets requires sticky sessions or a pub/sub broker (Redis)