The Last of the Snacks
Trust no one. Season aggressively.
End-to-end product: lobby, rooms, authoritative game server, validated messages, i18n (EN/ES), and production-style deploy (nginx/Traefik, blue/green). Shows full-stack ownership beyond CRUD APIs.
Overview
A browser-based, turn-based hidden-role card game for 4–8 players. Each session runs in a room with a dedicated game loop on the server: lobby configuration, turn timers, card resolution, spectators, and optional bots. The frontend is a polished Next.js experience; the backend is an authoritative Node server so clients never decide outcomes alone—exactly the split you want when interviewing for real-time or multiplayer systems.
Architecture
Monorepo (npm workspaces): @last-of-snack/shared (types and domain constants)
is built first and consumed by both apps. The game server (Fastify + WebSocket) owns rooms and state in memory. The frontend (Next.js 14 App Router)
drives UI, client state (Zustand), and can proxy HTTP to the game server for room creation in dev or behind nginx in prod.
Real-time flow & validation
- Connection: Clients open a WebSocket to the game server (
/ws); messages drive join, lobby settings, chat, start game, play card, restart, etc. - Authoritative state:
GameEngine, turn system, card resolution, and timers run on the server; broadcasts keep every client in sync (including spectators). - Schema-first protocol: Incoming payloads are validated with Zod before touching game logic—reduces abuse and makes the contract explicit for future features or mobile clients.
- HTTP edge: Room creation can go through Next.js
/api/rooms(proxy) or directly to the game server depending on environment—same pattern as many production BFF setups.
System design
flowchart TB
subgraph client [Browser]
UI[Next.js 14 App Router<br/>React + Zustand]
end
subgraph monorepo [npm workspaces monorepo]
SHARED["@last-of-snack/shared<br/>types, domain constants"]
FE[frontend]
GS[game-server]
SHARED --> FE
SHARED --> GS
end
UI -->|HTTP: pages, assets| FE
UI -->|HTTP: create room<br/>/api/rooms proxy| FE
FE -->|HTTP: rooms API| GS
UI -->|WebSocket /ws<br/>game messages| GS
subgraph gs_internal [game-server internals]
WS[Fastify + @fastify/websocket]
ZOD[Zod message validation]
RM[RoomManager / rooms]
GE[GameEngine, turns, timers, cards]
WS --> ZOD --> RM --> GE
end
GS --> WS Delivery & operations
Docker Compose runs frontend + game server + nginx locally; production paths include Traefik and a documented blue/green upgrade flow for zero-downtime swaps. Environment variables separate public client config from server-only secrets—familiar production hygiene.