FullStack Casino Platform
Production-quality 12-game online casino with MVC architecture and security hardening
Project Overview
Built as a software engineering portfolio project demonstrating advanced Node.js/Express architecture, MySQL database design, session management, real-time game state persistence, and modern vanilla JavaScript UI patterns. The platform features 12 casino games across table, card, machine, and instant categories — each with correct rule implementations and server-side state. Platform systems include atomic chip transactions with SELECT FOR UPDATE race condition prevention, a data-driven achievement engine (adding achievements = inserting a DB row), quadratic XP leveling, Chart.js bankroll/outcome charts, 5-category leaderboards, streak-based daily rewards, 6 CSS-variable theme system, and a full Blackjack AI coach with 6-deck S17 basic strategy and EV calculator.
The Problem
Building a multi-game casino platform requires correct rule implementations per game, secure atomic chip accounting, scalable session management, and meaningful player progression — all without introducing race conditions or security vulnerabilities.
The Solution
Designed an MVC + Service Layer architecture where game engines are pure functions isolated from the database. Instant games use a shared service that atomically handles bet validation, chip deduction, history, stats, XP, and achievements. Session games persist JSON state in game_sessions for mid-hand reconnection. All chip operations use SELECT FOR UPDATE transactions.
Key Highlights
- 12 games: Blackjack, European Roulette, Baccarat, Craps, Texas Hold'em, Video Poker, High/Low, Slots, Mines, Keno, Dice, Coin Flip
- MVC + Service Layer: game engines are pure functions with zero DB/HTTP access — fully unit-testable
- 67 unit tests covering Card, Hand, Deck, and Shoe classes in the game engine core
- MySQL session store with session fixation prevention (req.session.regenerate() on login)
- Atomic chip operations using SELECT FOR UPDATE inside transactions — no race conditions
- Data-driven achievement engine: adding achievements requires inserting a DB row, zero code changes
- Blackjack AI coach: full 6-deck S17 basic strategy table + EV calculator + win probability display
- Chart.js statistics dashboard: bankroll history, outcome breakdown, win/loss streaks
- 5-category leaderboards: chips, level, wins, blackjacks, streak
- 6 UI themes via CSS custom property overrides: Classic, Dark, Las Vegas, Neon, Royal Gold, Minimal
- Security: prepared statements, express-validator XSS sanitization, helmet CSP, timing-attack-safe auth, rate limiting
Data / Request Flow
System Architecture
Code Spotlight
Real snippets from the codebase demonstrating key engineering decisions.
1// Atomic chip operation — SELECT FOR UPDATE prevents race conditions
2async function processGameResult(userId, betAmount, outcome) {
3 return await db.transaction(async (trx) => {
4 // Lock the row for this transaction
5 const [profile] = await trx('profiles')
6 .where({ user_id: userId })
7 .forUpdate() // SELECT FOR UPDATE
8 .select('chips');
9
10 if (profile.chips < betAmount)
11 throw new Error('Insufficient chips');
12
13 const payout = outcome.win ? betAmount * outcome.multiplier : 0;
14 const delta = payout - betAmount;
15
16 await trx('profiles')
17 .where({ user_id: userId })
18 .increment('chips', delta);
19
20 // Immutable audit ledger
21 await trx('transactions').insert({
22 user_id: userId, amount: delta,
23 type: outcome.win ? 'win' : 'loss',
24 game: outcome.game, created_at: new Date(),
25 });
26
27 return { newBalance: profile.chips + delta, payout };
28 });
29}1// Pure function — no DB access, no HTTP. Fully unit-testable.
2function getBasicStrategyAction(playerHand, dealerUpcard, deckCount = 6) {
3 const total = playerHand.hardTotal();
4 const isSoft = playerHand.isSoft();
5 const isPair = playerHand.isPair();
6
7 if (isPair) return PAIR_STRATEGY[playerHand.cards[0].rank][dealerUpcard.rank];
8 if (isSoft) return SOFT_STRATEGY[total][dealerUpcard.rank];
9 return HARD_STRATEGY[total][dealerUpcard.rank];
10}
11
12function calculateEV(playerHand, dealerUpcard, betAmount) {
13 const action = getBasicStrategyAction(playerHand, dealerUpcard);
14 const winProb = WIN_PROBABILITY_TABLE[playerHand.hardTotal()][dealerUpcard.rank];
15 const ev = (winProb * betAmount) - ((1 - winProb) * betAmount);
16 return { action, ev: ev.toFixed(2), winProbability: (winProb * 100).toFixed(1) };
17}Challenges & How I Solved Them
Preventing chip race conditions when multiple requests arrive simultaneously — solved with SELECT FOR UPDATE inside MySQL transactions
Implementing timing-attack-safe login (dummy bcrypt hash for non-existent users)
Building a data-driven achievement system that works for any achievement type without code changes
Managing mid-hand reconnection for session-based games (Blackjack, Roulette, Mines, Hold'em)
Keeping 12 different game rule implementations correct and maintainable
Results
- →12 fully playable casino games with correct rule sets and server-side state management
- →67 passing unit tests on the core game engine (Card, Hand, Deck, Shoe)
- →Security hardened: prepared statements, CSP, rate limiting, session fixation prevention, XSS sanitization
- →Complete player progression: XP/levels, data-driven achievements, Chart.js dashboard, 5-category leaderboards