A production‑ready backend for a 1v1 real‑time head‑ball game. Exposes a REST API for auth, users, matches, and monitoring, plus Socket.IO for gameplay, matchmaking, rooms, and rematches. OpenAPI docs are built‑in.
- Tech stack: Node.js, Express, Socket.IO, MongoDB (Mongoose), JWT, SIWE, Swagger
- Docs: Swagger UI at
/api-docs - Runtime: CommonJS, Node 18+
- Web3 auth with SIWE: Nonce + signature verification; issues JWTs
- JWT‑secured endpoints: Bearer tokens for protected routes
- Real‑time gameplay: Join/create rooms, ready up, match start, scoring, timers
- Matchmaking & private rooms: Auto‑match or join by 6‑char room code
- Match records & stats: Persisted matches; user win/loss/draw totals
- Swagger/OpenAPI: Auto‑generated docs for REST endpoints
- CORS, rate limiting, security headers: Sensible defaults per environment
- Clone and install
npm install- Create
.env
# Server
PORT=3000
NODE_ENV=development
FRONTEND_URL=http://localhost:3000
# Database
MONGODB_URI=mongodb://localhost:27017/metahead_arena
# Auth
JWT_SECRET=replace-with-a-strong-secret- Run
# Development (nodemon)
npm run dev
# Production
npm start- Server listens on
http://localhost:${PORT} - Browse API docs at
http://localhost:${PORT}/api-docs
server.js # Entrypoint
src/app.js # Express app + HTTP server + wiring
src/config/database.js # MongoDB connection
src/config/socket.js # Socket.IO server + event handlers
src/config/swagger.js # Swagger/OpenAPI setup
src/middlewares/verify_login.middleware.js # JWT guard
src/routes/ # REST route modules (auth, user, match, game)
src/controllers/ # REST controllers
src/services/ # Core services (game, room manager, matches, users, auth)
src/models/ # Mongoose + in-memory models (User, Match, Player, GameRoom)
- Base path:
/api - Full, interactive docs: open Swagger UI at
/api-docs
POST /api/auth/nonce– Generate SIWE noncePOST /api/auth/verify– Verify SIWE message and return JWTPOST /api/auth/logout– Stateless logout success response
Example (verify):
curl -X POST http://localhost:3000/api/auth/verify \
-H 'Content-Type: application/json' \
-d '{
"message": "<SIWE message string>",
"signature": "0x..."
}'GET /api/users/wallet/{walletAddress}– Lookup by walletGET /api/users/profile/{userId}– Get profile + stats (JWT required)
Example (JWT protected):
curl http://localhost:3000/api/users/profile/<userId> \
-H 'Authorization: Bearer <JWT>'GET /api/matches/{matchId}– Match detailsGET /api/matches/user/{userId}/history?limit=&page=– Paginated history (JWT)GET /api/matches/user/{userId}/match/{matchId}– User’s perspective of match (JWT)
GET /api/game/stats– Live server statsGET /api/game/rooms/{id}/code– Retrieve shareable room code
Connect with a JWT via query or auth:
import { io } from "socket.io-client";
const socket = io("http://localhost:3000", {
query: { token: "<JWT>" },
// or
// auth: { token: "<JWT>" },
});
socket.on("welcome", console.log);join-game– Authenticate session and create Playerfind-match– Queue and auto‑join a room when readycreate-room– Create private roomjoin-room-by-code– { roomCode }player-ready– Toggle ready; auto‑start when both readygame-end– Submit final score/durationleave-roomrequest-rematch/decline-rematch- Gameplay relays:
move-left,move-right,jump,kick,stop-move,player-input,ball-state,player-position,powerup-spawned,powerup-collected
- Session/room:
welcome,player-created,room-created,room-joined,player-joined-room,player-left-room,room-full,error - Game flow:
player-ready,game-started,goal-scored,game-state,timer-update,timer-warning,time-up,game-ended,match-ended - Rematch:
rematch-requested,rematch-confirmed,rematch-declined,rematch-timeout
Minimal flow:
socket.emit("join-game");
socket.emit("find-match");
socket.on("room-full", () => socket.emit("player-ready"));
socket.on("game-started", () => {
// start sending input updates
socket.emit("move-left", { pressed: true });
});UserwalletAddress(unique, lowercased)gameStats:wins,losses,draws,totalMatches,matchHistory[]
Matchplayers[]with user ref, position, goalsresult:finalScore,duration,outcome,winner, wallet addressesstatus,startedAt,endedAt
Matches are created at game start, updated at game end, and users’ stats are incremented accordingly.
Environment variables used:
PORT– HTTP port (default 3000)NODE_ENV–developmentorproductionFRONTEND_URL– Allowed origin in production (CORS & Socket.IO)MONGODB_URI– MongoDB connection stringJWT_SECRET– Secret used to sign JWTs
CORS policy is permissive in development and restricted to FRONTEND_URL in production. Rate limiting is relaxed in development.
Security headers (CSP, HSTS, etc.) are applied in production. Helmet is available and can be enabled in src/app.js if desired.
- Start the server even without MongoDB: gameplay via Socket.IO works; persistence requires MongoDB
- Swagger docs are generated from annotations in
src/routes/*.js - Socket.IO configuration (CORS origins, ping timeouts) is centralized in
src/config/socket.js
{
"dev": "NODE_ENV=development nodemon server.js",
"start": "node server.js"
}- Set
NODE_ENV=production, configureFRONTEND_URL,JWT_SECRET,MONGODB_URI - Expose
${PORT} - Swagger UI remains available at
/api-docsunless you gate it
ISC © MetaHead Arena
Issues and PRs are welcome. Please keep commit messages and PR descriptions concise and focused on the “why” and the user‑visible impact.