A Blackjack game built entirely with TypeScript, featuring clean architecture, reusable domain logic, real-time gameplay, and a production deployment.
π Live Demo: https://blackjack-ts.onrender.com
Blackjack-ts is a web implementation of the Blackjack card game, designed not just as a playable game, but as a software architecture showcase.
The application is split into three main layers:
- Domain β Pure game logic (cards, deck, players, rules)
- Backend β API + WebSocket server (Node.js)
- Frontend β Graphical user interface (React)
The project emphasizes:
- Clean separation of concerns
- Domain-driven design principles
- Type safety across the entire stack
- Real-time communication using WebSockets
- A production-ready deployment
The same domain logic originally developed for a CLI version is reused in a web-based, real-time environment without modification.
root
βββ domain β Pure game rules and entities
βββ backend β HTTP API + WebSocket server
βββ frontend β React UI (Vite)
βββββββββββββββββββββββββββ
β Frontend β
β React + TypeScript β
β (Vite) β
β β
β - UI Components β
β - Custom Hooks β
β - WebSocket Client β
β - REST Client β
βββββββββββββ¬ββββββββββββββ
β
HTTP API β WebSocket
β
βββββββββββββΌββββββββββββββ
β Backend β
β Node.js + TypeScript β
β Express + WS β
β β
β - Controllers β
β - Routes β
β - WebSocket Server β
β - Game State Store β
βββββββββββββ¬ββββββββββββββ
β
β Calls domain logic
β
βββββββββββββΌββββββββββββββ
β Domain β
β Pure TypeScript β
β β
β - Cards / Deck β
β - Players / Dealer β
β - Game Rules β
β - Hand Evaluation β
β β
β (Framework-agnostic) β
βββββββββββββββββββββββββββ
- Domain logic is isolated and reusable
- Backend acts as an orchestrator, not a rule holder
- Frontend focuses only on UI and interaction
- Enables future scalability:
- Multiplayer tables
- Persistence
- Authentication
- Mobile or CLI clients
This mirrors real-world backend/frontend/domain separation used in production systems.
The domain layer contains all Blackjack rules and entities, fully independent from any framework or transport.
Includes:
- Card and deck modeling
- Hand evaluation logic
- Player and dealer behavior
- Game flow and rule enforcement
Key characteristics:
- No HTTP
- No WebSockets
- No React
- No infrastructure code
Built with Node.js + TypeScript.
Responsibilities:
- Expose game actions via a REST API
- Manage real-time gameplay via WebSockets
- Maintain authoritative game state on the server
- Bridge communication between frontend and domain
Key concepts implemented:
- Express routing and controllers
- Typed request/response contracts
- Centralized in-memory game state
- WebSocket event lifecycle management
The backend does not contain game rules β it delegates all logic to the domain layer.
Built with React + TypeScript, bundled with Vite.
Responsibilities:
- Render the Blackjack table, cards, and game state
- Handle player interactions (hit, stand, start game, etc.)
- Synchronize state with the backend via WebSockets
- Trigger HTTP requests when needed (game initialization, resets)
Key concepts:
- Functional components
- Custom hooks (
useGame,useWebSocket) - Typed state and props
- Clear separation between UI and networking logic
The UI is intentionally simple for now, with plans for further polish and animations.
Decision: Use TypeScript in domain, backend, and frontend.
Why:
- End-to-end type safety
- Shared mental model across layers
- Fewer runtime errors
- Better refactoring and scalability
Trade-off: Slightly higher upfront complexity, but worth it for maintainability.
Decision:
Extract all game rules into a standalone /domain package.
Why:
-
Business rules are the most valuable part of the system
-
Domain logic should not depend on frameworks
-
Enables reuse across:
- CLI
- Backend
- Tests
- Future services
Result: The backend becomes an orchestrator, not a rule holder.
Decision: Store and manage game state exclusively on the server.
Why:
- Prevents client-side cheating
- Enables real multiplayer in the future
- Simplifies synchronization logic
- Matches real-world multiplayer game architecture
Alternative considered: Client-side state with validation β rejected due to consistency and security concerns.
Decision: Use WebSockets instead of polling or HTTP-only updates.
Why:
- Blackjack is event-driven
- Immediate feedback improves UX
- Scales naturally to multiplayer tables
- Matches real-time system design patterns
Usage:
- Player actions β server
- State updates β broadcast to client(s)
Decision: Use both REST and WebSockets instead of only one.
Why:
-
REST is ideal for:
- Health checks
- Game initialization
- Stateless operations
-
WebSockets are ideal for:
- Continuous game updates
- Real-time interaction
This avoids forcing one protocol to do everything.
Decision:
Create the HTTP server manually instead of relying on app.listen().
const server = http.createServer(app);
initGameSocket(server);Why:
- Required for clean WebSocket integration
- Enables future protocol-level configuration
- Aligns with production Node.js patterns
Decision: Use environment variables for API and WebSocket URLs.
Why:
- Clean separation between local and production environments
- No hardcoded URLs
- Avoids common deployment pitfalls
This decision directly solved the production bug encountered during deployment.
Decision: Deploy frontend and backend under the same public domain on Render.
Why:
- Simplifies CORS
- Simplifies WebSocket configuration
- Reduces operational overhead
- Ideal for small-to-medium applications
Scales later to: Separate services, Docker, or microservices if needed.
The project uses environment variables to correctly separate local development and production environments.
Local development (.env, not committed):
VITE_API_URL=http://localhost:3001/api
VITE_WS_URL=ws://localhost:3001Production (configured on Render):
VITE_API_URL=https://blackjack-ts.onrender.com/api
VITE_WS_URL=https://blackjack-ts.onrender.comThe application is deployed on Render as a full-stack service.
- Single public URL serving both frontend and backend
- Backend listens on the port provided by Render
- Frontend build served as static assets
- WebSockets enabled and working in production
- Environment variables configured via Render dashboard
π Live URL: https://blackjack-ts.onrender.com
- Node.js (v18+ recommended)
- npm or yarn
npm installcd backend
npm run devcd frontend
npm run devMake sure the frontend .env file is configured correctly for local development.
- TypeScript
- Node.js
- Express.js
- WebSockets
- REST APIs
- React
- Vite
- WebSocket client
