CodeArena is a real-time, multiplayer competitive programming platform built around live 1v1 battles. Players queue into ranked matches, solve the same programming problem under a time limit, and climb a visible leaderboard based on Elo-style rating changes.
The app is split into a Next.js frontend, an Express backend, PostgreSQL for persistence, Redis for realtime/matching workflows, and Docker-based submission judging.
- Live matchmaking for ranked 1v1 battles
- Timed battle arena with a code editor, test panel, opponent progress, and match timer
- Automatic judging of C++ submissions through short-lived Docker containers
- Non-blocking submission flow: battle submits are saved first, then judged in the background while the UI polls for the final verdict
- Post-match AI coach reviews generated after the official match result is already saved
- Public problems browser and problem detail pages
- Leaderboards, match history, user profiles, and dashboard views
- Authentication with email/password flow and Google OAuth support
- Realtime updates over Socket.IO for matchmaking and battle state
- Safe session cleanup on sign out, including queue cleanup and stale waiting-match cancellation
- Frontend: Next.js 16, React 19, TypeScript, Tailwind CSS
- Backend: Node.js, Express, Socket.IO, TypeScript
- Database: PostgreSQL with Prisma
- Cache / realtime support: Redis
- Judging: Docker runner using
gcc:13-bookworm - AI reviews: Gemini API via the standalone worker
- UI / motion: Framer Motion, Lucide icons, custom design system components
frontend/: Next.js app with landing pages, auth, dashboard, battle arena, and shared UIbackend/: API, auth, matchmaking, submissions, problem services, and realtime socket serverworker/: background worker for AI battle reviews, with optional legacy Judge0 queue supportdocker-compose.yaml: local production-style stack for frontend, backend, worker, Postgres, and Redis
- Visit the landing page and explore the product
- Register or log in
- Browse curated problems
- Join matchmaking and enter a ranked battle
- Both players enter the arena; once both have joined, the match becomes active and the timer starts
- Solve the problem while the timer and opponent progress stay live
- Run code against public tests or submit code for full judging
- Review rating changes, match result, AI coach feedback, match history, and leaderboard position
Players join the Redis-backed matchmaking queue with their rating and preferred difficulty. The backend pairing loop selects compatible players, creates a Match and two MatchParticipant records, stores live match state in Redis, and emits match_found events over Socket.IO.
A match starts only after both players enter the battle page. Until then it remains WAITING, and the editor disables Run and Submit with a waiting state. When both players join, the backend marks the match ACTIVE, starts the timer, writes the start time to the database, and publishes realtime timer sync events.
If a user signs out or leaves during a waiting match, the backend cleans queue/presence state and cancels stale waiting matches so neither account gets trapped as "already in a match".
Run and Submit intentionally behave differently:
- Run checks only public test cases and returns synchronously. It is for quick feedback while solving.
- Submit creates a persistent
Submissionwith statusPENDING, returns immediately, and starts full judging in the background. - The frontend stores the pending submission and polls
GET /api/submissions/:iduntil the final verdict arrives. - When judging finishes, the backend updates the submission, publishes realtime submission progress, updates match participant progress, and finishes the match if the submission is accepted.
This keeps the UI responsive even when hidden tests take longer than the browser API timeout.
The judge result is the source of truth. When a match ends, the backend calculates the winner from accepted submissions or timeout progress, applies Elo changes, saves final participant results, clears active-match Redis keys, and publishes the match_result event.
AI reviews are a separate post-match feature. They do not decide the winner, affect rating, delay the result modal, or block users from queueing again.
After the match result is saved, the backend creates one AiBattleReview record per player with status PENDING and pushes jobs into Redis. The worker consumes ai_review_queue, loads safe match context, calls Gemini for structured JSON feedback, and saves the completed review. The frontend result modal polls GET /api/matches/:matchId/ai-review and shows pending, completed, failed, and retry states.
The AI prompt excludes hidden test cases, JWTs, emails, secrets, private user data, and full internal logs. It only receives problem metadata, final code, judge verdicts, test counts, timings, and high-level failure reasons.
Frontend routes currently include:
- Public: home, about, leaderboard, problems list, problem details, and profile
- Auth: login and register
- Dashboard: overview, matchmaking, problems, history, leaderboard, and settings
- Arena: battle room for an active match
- Node.js 22 or newer
- npm 10 or newer
- Docker Desktop or Docker Engine
- Docker Compose
If you plan to run the backend outside Docker, Docker must still be available because the submission judge launches temporary containers.
Configure backend/.env with the following variables:
DATABASE_URL: PostgreSQL connection stringREDIS_URL: Redis connection stringFRONTEND_URL: public frontend origin used for CORS and redirectsPORT: backend port, usually5000NODE_ENV: useproductionfor Docker / deploymentJWT_SECRET: signing secret for auth tokensJUDGE_WORK_DIR: workspace used by the judge runner, usually/judge-workJUDGE_DOCKER_VOLUME: Docker volume name used for judge work filesJUDGE_DOCKER_IMAGE: compilation/runtime image, usuallygcc:13-bookwormAI_REVIEW_ENABLED: set totrueto enqueue post-match AI reviews, orfalseto disable them without affecting judgingGEMINI_API_KEY: Gemini API key used by the worker for AI reviewsGEMINI_AI_REVIEW_MODEL: primary review model, usuallygemini-2.5-flashGEMINI_AI_REVIEW_FALLBACK_MODEL: fallback model, usuallygemini-2.5-flash-liteAI_REVIEW_TIMEOUT_MS: optional Gemini request timeoutCODE_EXECUTION_WORKER_ENABLED: optional; set totrueonly if using the worker's legacy Judge0 submission queueGOOGLE_CLIENT_ID: Google OAuth client idGOOGLE_CLIENT_SECRET: Google OAuth client secretGOOGLE_REDIRECT_URI: OAuth callback URL
Configure frontend/.env with:
NEXT_PUBLIC_API_URL: public API base URL exposed to the browserNEXT_PUBLIC_SOCKET_URL: public Socket.IO URL exposed to the browserBACKEND_INTERNAL_URL: backend URL used inside Docker during build/runtime
The default Docker stack starts the worker for AI review jobs. It reads runtime environment from Compose and backend/.env.
For AI reviews, configure:
DATABASE_URLREDIS_URLSOCKET_URLGEMINI_API_KEYGEMINI_AI_REVIEW_MODELGEMINI_AI_REVIEW_FALLBACK_MODELAI_REVIEW_TIMEOUT_MS
The worker also contains optional legacy Judge0 queue support. Only use these if CODE_EXECUTION_WORKER_ENABLED=true:
JUDGE0_BASE_URLJUDGE0_LANGUAGE_ID_CPPJUDGE0_REQUEST_TIMEOUT_MSJUDGE0_POLL_INTERVAL_MSJUDGE0_MAX_WAIT_MSJUDGE0_JOB_MAX_ATTEMPTSJUDGE0_AUTH_TOKEN
git clone <repo-url>
cd CodeArenaThis is the easiest way to run the app locally and the same path I validated for production-style builds.
docker compose up --buildThat starts:
- frontend on
http://localhost:3000 - backend on
http://localhost:5000 - worker for background AI reviews
- PostgreSQL on
localhost:5432 - Redis on
localhost:6379
- Backend health:
http://localhost:5000/health - Frontend:
http://localhost:3000
If you prefer to run services separately:
cd frontend
npm install
npm run devcd backend
npm install
npm run devThe backend expects PostgreSQL, Redis, and Docker to be available.
cd worker
npm install
npm run devWhen running the worker outside Compose, make sure it can reach the same DATABASE_URL and REDIS_URL as the backend. For AI reviews, GEMINI_API_KEY must be set. For the default app flow, leave CODE_EXECUTION_WORKER_ENABLED unset or false.
- Frontend production build:
cd frontend && npm run build - Backend TypeScript build:
cd backend && npm run build - Worker TypeScript build:
cd worker && npm run build - Full container build:
docker compose build - Full stack smoke test:
docker compose up -d
The backend includes Prisma scripts for schema deployment and seeding.
cd backend
npm run db:setupIf you only need the seed data:
cd backend
npm run seednpm run dev: start the Next.js dev servernpm run build: create the production buildnpm run start: run the built appnpm run lint: run ESLint
npm run dev: start the API in watch modenpm run build: compile TypeScriptnpm run start: run the compiled servernpm run db:setup: apply Prisma migrations and seed the databasenpm run seed: run the database seed
npm run dev: start the worker in watch modenpm run build: compile the workernpm run start: run the compiled worker
- The repo is already set up for Docker-based deployment.
backend/Dockerfileinstalls Docker CLI support because the judge runs temporary containers from inside the backend container.frontend/Dockerfilebuilds the Next.js app withBACKEND_INTERNAL_URLbaked in at build time.docker-compose.yamlwires the services together with health checks and the local production-style environment.
- If
frontendbuild output gets stuck on Windows, remove the local.nextdirectory or stop any process locking it and rebuild. - If submissions fail to run, confirm Docker Desktop or Docker Engine is available to the backend container or host process.
- If Submit is disabled in battle, confirm both players have entered the battle page and the match timer has started. Submit is blocked while the match is still
WAITING. - If Submit returns
PENDINGbut never updates, check backend logs and confirm the backend container can launch Docker judge containers. - If users see "already in a match" after signing out, restart with the latest backend changes. Logout now clears queue/presence state and stale waiting matches are cancelled automatically.
- If AI review stays pending, check the worker logs and confirm
AI_REVIEW_ENABLED=true,GEMINI_API_KEYis set, and Redis is reachable. - If the worker logs Judge0 readiness errors, leave
CODE_EXECUTION_WORKER_ENABLEDunset or set it tofalseunless you intentionally run the legacy Judge0 queue. - If login redirects fail, double-check
FRONTEND_URLand the Google OAuth callback URL. - If realtime features do not connect, verify
NEXT_PUBLIC_SOCKET_URL,REDIS_URL, and the backend CORS origin.
- The default compose stack uses backend-managed Docker judging for C++ submissions.
- The default worker processes AI review jobs. Judge0 queue processing is opt-in through
CODE_EXECUTION_WORKER_ENABLED=true. - AI reviews are generated once, saved in PostgreSQL, and reused by the result page.
- Local compose creates persistent volumes for PostgreSQL and the judge workspace.