Architecture and code structure
This page explains how Blaster is built: the tech stack, project layout, migration system, API routes, and how the React UI and Phaser game work together.
Blaster game series
- Blaster arcade game
- Blaster setup
- Blaster gameplay and features
- Blaster architecture - You are here
- Blaster deployment
1. Tech stack
Blaster uses:
- Next.js 14.2.18 for the application framework (app router).
- Phaser 3 for the arcade game engine.
- PostgreSQL 15 with the
pgclient library. - Clerk v6 for authentication.
- Tailwind CSS and CSS modules for styling.
- TypeScript for types across the app and APIs.
The goal is a modern but understandable stack that is easy to run locally and in Kubernetes.
2. Project structure
Key folders in the repo:
blaster/
├── app/
│ ├── api/ # API routes (database operations)
│ │ ├── high-scores/ # High score management
│ │ ├── profile/ # User profile operations
│ │ └── user/ # User authentication
│ ├── components/ # React & Phaser game components
│ ├── styles/ # CSS modules
│ └── utils/ # Database client & game logic
├── db/
│ └── migrations/ # Versioned SQL migrations
├── lib/
│ └── db/ # Database infrastructure (pool, migration logic)
├── scripts/
│ └── migrate.ts # Migration CLI
└── public/
└── sounds/ # Game audio assets
The database/ directory holds an older schema file and is kept mainly for reference; the active schema lives in the migrations.
3. Database and migrations
Blaster uses a custom migration system built on plain SQL and a simple tracking table:
- Migration files live under
db/migrations/with numeric prefixes (001_init.sql,002_add_feature.sql, and so on). - A
schema_migrationstable records the versions that have been applied. - Migrations are idempotent: they use
IF NOT EXISTSguards so they can be re-run safely. - Each migration runs in a transaction so it either completes fully or not at all.
Supporting code:
lib/db/pool.tsprovides a singleton PostgreSQL connection pool.lib/db/migrate.tscontains the logic to discover and apply migrations.scripts/migrate.tsexposes a CLI withnpm run migrateandnpm run migrate:status.
In Kubernetes, a dedicated initContainer runs npm run migrate before the main app starts, so the schema is correct before any traffic hits the game.
4. API routes and data flow
All database access goes through API routes under app/api/:
/api/userhandles authentication and basic user profile initialisation./api/profilemanages user profiles./api/high-scoresdeals with high score submission and retrieval./api/statsreads and writes player statistics./api/achievementsand/api/achievements/checkmanage achievement definitions and awards./api/leaderboardsprovides leaderboard views across multiple ranking types.
This keeps database credentials on the server side only and avoids bundling the pg client into the browser.
The flow in a typical game looks like:
- Player signs in via Clerk.
- The UI calls user/profile APIs to ensure a profile exists.
- During and after a game, stats and achievements are updated via API calls.
- Leaderboards and stats views are read back via the relevant endpoints.
5. React, Phaser, and game lifecycle
The core components are:
app/page.tsx: the main entry point, responsible for loading user state and deciding what to show.Gamecomponent: orchestrates gameplay state (character selection, playing, game over) and communicates between React and Phaser.PhaserGamecomponent: wraps the Phaser instance, handles creation, updates, and cleanup.
A key design decision is to keep props passed into the Phaser wrapper stable so the game is not unnecessarily torn down and recreated. Where needed, constants are moved outside React components or memoised so the Phaser instance can stay alive through renders.
Supporting UI components (stats modal, achievements panel, leaderboards, settings menu, game over screen) are built as React components, often rendered via portals so they sit cleanly over the canvas without z-index issues.
6. Logging and debugging
The game uses emoji-prefixed console logs to help trace different parts of the flow during development, for example:
- Profile checks, game initialisation, hit events, lives changes, game over, and cleanup.
This makes it easy to see which part of the pipeline is firing without reading long log prefixes.
7. Security and Docker concerns
From an application architecture perspective:
- All database operations are parameterised to avoid SQL injection.
- Secrets are passed through environment variables (local
.env.local, Kubernetes Secrets in clusters). - The
.dockerignorefile is configured so.git/,.env*, editor settings, and other development artefacts are not copied into images.
For cluster-level security, see the Blaster GitOps and Kubernetes runbooks, which cover SOPS, Secrets, registry credentials, and network concerns.