NestJS WebSocket server that powers the referee light system.
Part of the OpenRef Lights monorepo . See the root README for full project setup.
The backend provides real-time communication for the referee light system using WebSockets. It manages the lift decision lifecycle through a custom state machine, ensuring all connected clients stay synchronized.
Key responsibilities:
- Manage referee connections and disconnections
- Track decisions from all three referee positions
- Handle jury overrule functionality
- Broadcast state changes to all connected clients
- NestJS 11: Progressive Node.js framework
- Socket. IO 4: Real-time bidirectional communication
- TypeScript 5: Type-safe development
- Jest : Testing framework
- Node.js 20.0.0 or higher
- npm 9.0.0 or higher
If installing from the monorepo root, dependencies are automatically installed.
From monorepo root (recommended):
npm installStandalone:
cd backend
npm installCreate a .env file in the backend directory:
# Server port
PORT=3000
# Environment
NODE_ENV=development
# CORS configuration
CORS_ORIGIN=*
# Authentication token (optional)
# Provides basic access control - see Security note below
# Leave empty to disable, or generate with: openssl rand -hex 32
AUTH_TOKEN=Security Note: The AUTH_TOKEN provides protection against casual unauthorized access but is transmitted in plaintext over HTTP/WebSocket. This is suitable for isolated/private networks (typical competition setup). For untrusted networks or internet-facing deployments, use HTTPS. See the main README Security Considerations for details.
Development (with hot reload):
npm run start:devDebug mode:
npm run start:debugBuild for deployment:
npm run build
npm run start:prodFrom monorepo root:
npm run dev:backendThe server starts on http://localhost:3000 by default.
The lift lifecycle is managed by a custom state machine with five states:
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ awaitingDecisions ──► collectingDecisions ──► readyToReveal ──► revealingDecisions │
│ ▲ │ │ │ │
│ │ │ │ │ │
│ │ ▼ ▼ ▼ │
│ │ juryOverrule ◄──────────────┴──────────────────┘ │
│ │ │ │
│ └──────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────────────────────┘
| State | Description |
|---|---|
awaitingDecisions |
Initial state. Waiting for referees to connect. |
collectingDecisions |
Referees connected. Waiting for decisions. |
readyToReveal |
All decisions submitted. Ready to display. |
revealingDecisions |
Lights visible to audience. |
juryOverrule |
Jury has overridden the referee decision. |
Connect to the WebSocket server:
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000', {
auth: {
token: 'your-auth-token',
}, // Optional
});| Event | Payload | Description |
|---|---|---|
join |
{ position?: 'left' | 'chief' | 'right' } |
Join session (position for referees) |
decision |
{ position: RefereePosition, decision: Decision } |
Submit a referee decision |
resetRefereeDecision |
{ position: RefereePosition } |
Retract a referee decision |
revealDecisions |
- | Reveal decisions to audience |
resetAll |
- | Reset system to awaiting decisions |
juryOverrule |
{ decision: Decision } |
Jury overrules referees |
clearJuryOverrule |
- | Remove jury overrule |
| Event | Payload | Description |
|---|---|---|
stateUpdate |
{ state: LiftState, context: LiftContext } |
Full state snapshot after every change |
error |
{ message: string } |
Error occurred (e.g. invalid auth token) |
enum Decision {
WHITE = 'white', // Good lift
RED = 'red', // No lift reason 1
BLUE = 'blue', // No lift reason 2
YELLOW = 'yellow', // No lift reason 3
}
enum RefereePosition {
LEFT = 'left',
CHIEF = 'chief',
RIGHT = 'right',
}Run all tests:
npm testWatch mode:
npm run test:watchWith coverage:
npm run test:covCoverage reports are generated in the coverage/ directory.
Build the image:
docker build -t openref-lights-backend .Run the container:
docker run -p 3000:3000 \
-e PORT=3000 \
-e NODE_ENV=production \
-e CORS_ORIGIN=* \
openref-lights-backend| Script | Description |
|---|---|
npm run start:dev |
Start with hot reload |
npm run start:debug |
Start in debug mode |
npm run start:prod |
Start production build |
npm run build |
Build for production |
npm test |
Run tests |
npm run test:watch |
Run tests in watch mode |
npm run test:cov |
Run tests with coverage |
npm run lint |
Lint code |
npm run format |
Format code with Prettier |
- Root README: Full project overview
- Frontend README: Angular application