Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Antonelli-Tech-Solutions/spades/llms.txt

Use this file to discover all available pages before exploring further.

Spades Online runs as a single Node.js process (ES Modules, no build step). An Express HTTP server handles all REST API routes and serves the static web client from client/web/. A WebSocket server, built on the ws library, shares the same port by default — the same http.Server instance accepts both HTTP requests and WebSocket upgrade requests. Redis is responsible for all ephemeral runtime state: sessions, table data, game state, presence, pub/sub fan-out between server instances, and rate-limit counters. PostgreSQL is the durable store for everything that must survive a restart: player accounts, profiles, game history, friendships, and moderation data.

Layers

HTTP Server

Express handles all /api/* routes — authentication, table management, game actions, social features, and build info. It also serves client/web/ as static files and injects runtime configuration (such as WS_URL for split-host deployments) into the HTML before serving it.

WebSocket Engine

Real-time game events flow over the WebSocket server. Clients join table rooms (table:{tableId}) or the global lobby channel (lobby) by sending JOIN / JOIN_LOBBY messages. The server broadcasts events — CARD_PLAYED, TURN_CHANGED, HAND_DEALT, TABLE_UPDATED, and more — to every subscriber in the relevant room.

Redis

Redis stores sessions, active table and game state, presence records (presence:{playerId}), join-link and spectator-link tokens, invite records, and per-IP rate-limit counters. It also acts as the pub/sub broker: each table room and personal notification channel maps to a Redis channel that all server instances subscribe to, enabling horizontal scaling without sticky sessions.

PostgreSQL

PostgreSQL holds the five durable schemas applied by the numbered migration files: players and email-verification tokens, player profiles and game history, password-reset tokens, friendships, and player blocks. Game results are written to PostgreSQL when a hand completes; everything else stays in Redis until the table is terminated.

Request flow

The following steps trace a card play from the client HTTP request to all connected browsers receiving the event.
1

Client sends HTTP POST

The client posts POST /api/tables/:tableId/play with headers x-session-id and x-player-id, and a JSON body { "card": "AS" }.
2

Server validates the request

server.js validates the session against Redis, checks the player is seated at the table (getSeatForPlayer), loads the game state from Redis (getGameState), and runs anti-cheat validation (validateCardPlay) to confirm it is the player’s turn and the card is a legal play.
3

State is updated in Redis

playCard returns a new immutable game state. The updated state is written back to Redis via saveGameState. Presence records are not modified by card plays; they are updated on connect, disconnect, game start, and game end.
4

WebSocket broadcast via Redis pub/sub

wss.broadcast(tableId, 'CARD_PLAYED', payload) publishes the event JSON to the Redis channel table:{tableId}. Every server instance that has at least one local WebSocket client subscribed to that table channel receives the published message on its dedicated subscriber connection.
5

Clients receive the event

Each server instance’s onChannelMessage handler forwards the JSON string to every open WebSocket in the matching room — except observer (_isObserver) connections when the event type is HAND_DEALT, HAND_REVEALED, or BLIND_NIL_EXCHANGE_PROMPT. All clients render the updated game state from the event payload.

Deployment modes

Spades Online supports two WebSocket deployment topologies. The default single-host mode requires no extra configuration. Split-host mode is used when the HTTP frontend is hosted on one provider (e.g. Vercel) and the WebSocket server runs separately (e.g. Railway), requiring a reverse proxy to route upgrade requests.
When WS_PORT is unset or equal to PORT, the WebSocket server attaches to the same http.Server instance as Express. No reverse proxy is needed — the same TCP port accepts both HTTP and WebSocket upgrade requests.
# Required
export DATABASE_URL="postgresql://user:password@localhost:5432/spades"
export REDIS_URL="redis://localhost:6379"

# Optional — defaults to 3000
export PORT=3000

npm start
The client connects to window.location.host, which already points to the correct port.

Module map

ModuleFileResponsibility
Express entry pointserver/app.jsCreates the Express app and HTTP server, configures CORS, serves static files, injects WS_URL, wires together the HTTP and WebSocket servers, calls handler()
Route handlersserver/server.jsAll /api/* route handlers — auth, social, tables, game actions, build info
PostgreSQL poolserver/db.jsShared pg.Pool singleton; getDb() returns the pool, closeDb() tears it down
Redis clientserver/redis.jsShared redis client singleton; getRedis() returns a connected client, closeRedis() tears it down
Presence engineserver/presence.jsRedis-backed state machine: setPresenceOnline, setPresencePlaying, clearPresence, setPresenceOnConnect (reconciles seat state on reconnect)
WebSocket serverserver/ws/index.jsAuthenticated upgrade, room management, Redis pub/sub fan-out, heartbeat, broadcast, broadcastLobby, sendToPlayer, notifyPlayer
Authserver/auth/*Registration, email verification, login, session management, password reset, email sending
Game engineserver/game/*Game state machine, bidding, deck, trick resolution, scoring, bot logic
Socialserver/social/*Player profile data access, blocking logic
Lobbyserver/lobby/table.jsTable creation, seat management, lobby index, join/spectator links
Anti-cheatserver/anticheat/validate.jsServer-side move validation: turn enforcement, card legality

Next steps

Database

PostgreSQL schema details, all five migration files, and notes on what lives in Redis instead.

WebSocket

Connection authentication, room types, Redis pub/sub fan-out, observer filtering, heartbeat, and server-side helper methods.

Build docs developers (and LLMs) love