Team Quiz β€” Technical Overview

A real-time, two-team quiz application built for early-talent onboarding sessions, and hardened from working prototype to a professional, maintainable tool.

Node.js Express Socket.IO Helmet pino Terraform AWS EC2 nginx + HTTPS GitHub Actions

Author: Max Danieli Β· Onboarding programme tooling

1. What it is 2. How the game works 3. Architecture 4. Question sets 5. Front-ends 6. Security & hardening 7. Testing 8. Code quality & CI 9. Deployment & operations 10. Project structure 11. Design decisions 12. Possible next steps

1. What it is

Team Quiz is a live, browser-based quiz for onboarding groups of new hires. Players join from any device, are split into two random teams, and answer technical questions together. After every round the correct answer and a short explanation are shown, so the game works as a learning moment rather than a pure competition. A separate host panel drives the session and chooses from several question sets spanning Java/DevOps, Git, Linux, AWS, AI and security.

The random-teams-plus-discussion format is deliberate: it gets graduates talking, reasoning aloud and meeting each other, which is the real goal of an onboarding activity.

2. How the game works

Each room holds one game as a small state machine with five phases: login β†’ ready β†’ question β†’ reveal β†’ gameover. Many rooms run concurrently, one per host, each with its own 4-digit join code.

Throughout, the host sees the correct answer (to read aloud); players never receive the answer or explanation until the reveal β€” this is enforced server-side, not just hidden in the UI.

3. Architecture

It is a single Node.js process. Real-time state is pushed to every connected client over a WebSocket (Socket.IO); the only persistence is a small JSON file recording which questions each set has already asked. TLS is terminated by nginx in front of the app.

 Players' phones / laptops          Host laptop
        (index.html)                (host.html)
             β”‚                          β”‚
             β”‚        WebSocket (Socket.IO) over HTTPS
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β–Ό
                    nginx (TLS, reverse proxy)
                           β–Ό
                 Node.js  ── Express (static + /config + /healthz)
                          ── Socket.IO handlers
                          ── GameEngine (in-memory state machine)
                          ── store.js (atomic JSON file: used-question history)
                           β–Ό
                   AWS EC2 (Ubuntu, systemd service)

The application code is split into small modules with single responsibilities. The game rules live in a transport-agnostic GameEngine, so the logic can be tested without any network, and the socket layer is a thin adapter that calls engine methods and broadcasts the resulting state.

4. Question sets

Questions live as one JSON file per set in a questions/ directory. The server auto-discovers them at startup, so adding a new set is just dropping in a file β€” no code change. Each set carries an id, display name, description, ordering and its list of questions (each with four options, the correct index and an explanation).

The host chooses the active set from a dropdown in the lobby. Crucially, the β€œalready asked” history is tracked per set, so every set exhausts its own pool independently and questions don't repeat until a set runs out. The shipped content is 18 sets β€” 664 questions β€” covering Java/DevOps, Git, Linux, AWS, Azure, Networking, Security, Containers & Kubernetes, CI/CD & IaC, Databases, Observability, Python, AI foundations, general tech trivia and a few harder rounds for fun.

5. Front-ends

Three plain HTML/CSS/JS pages share one dark, GitHub-inspired theme:

There is no front-end framework or build step; the pages talk to the server purely over Socket.IO and a tiny /config endpoint.

6. Security & hardening

The site is publicly reachable, so it is treated as internet-exposed rather than a trusted internal tool. The hardening applied:

7. Testing

Tests use Node's built-in runner (node --test) with no external framework, on two levels:

Together this is a fast, meaningful safety net β€” the refactor into modules was validated entirely by these tests staying green.

8. Code quality & CI

ESLint and Prettier enforce a consistent style, and a GitHub Actions workflow runs lint, format check and the full test suite on every push and pull request. The project is no longer a one-person black box: another engineer can clone it, run npm ci, and get immediate feedback.

9. Deployment & operations

Infrastructure is described in Terraform: an EC2 instance with a static IP and security group, provisioned with nginx as a TLS-terminating reverse proxy and a Let's Encrypt certificate. DNS points a subdomain at the instance. The app runs as a systemd service, with configuration (win score, passwords, data-file path) supplied through an environment file.

Day-to-day content and code updates are pushed to the instance directly and the service restarted; the persistent question-history file lives outside the app directory so it survives redeploys. A Dockerfile and a Kubernetes/Helm chart are also included, giving a clear path to run the same image on the EKS platform later if desired.

10. Project structure

server.js            entry point: express app, helmet, Socket.IO, listen, shutdown
store.js             per-set "already asked" history (atomic JSON writes)
src/
  config.js          environment + paths
  logger.js          pino logger
  questions.js       loads question sets from a directory
  game.js            GameEngine β€” the game state machine (no transport concerns)
  socketHandlers.js  wires Socket.IO events to the engine
  validation.js      name / answer input helpers
  teamNames.js       team-name pool
  util.js            shuffle
questions/           one JSON file per question set
public/              player + host front-ends, styles, these docs
test/                unit + integration tests
infra/               Terraform (EC2 + nginx + HTTPS)
deploy/helm/         Kubernetes chart for the EKS path

11. Design decisions & trade-offs

File-based persistence, not a database
The only durable state is a short list of used question ids per set. A database would be over-engineering; the store is a small module with a clean interface, so it can be swapped for SQLite or Redis later without touching game logic.
Single process, in-memory game state
A live session is one room with a handful of teams. Keeping state in memory keeps the code simple and fast; the deliberate cost is that a restart ends an in-progress match (the question history survives).
Transport-agnostic engine
Putting all rules in a plain class β€” separate from sockets β€” is what makes the logic unit-testable and the socket layer trivially thin.
Pragmatic Content-Security-Policy
The pages use inline scripts and styles, so the CSP allows those while locking everything else to same-origin. Tightening it further would mean externalising those scripts β€” a worthwhile but non-urgent follow-up.

12. Possible next steps

← Back to Play How to run it β†’

Team Quiz Β· technical overview