| name | dev-multiplayer-colyseus-server |
| description | Colyseus server setup, room handlers, lifecycle events, and scaling. Use when setting up multiplayer server. |
| category | multiplayer |
Colyseus Server Setup
Node.js multiplayer framework - authoritative game server with real-time state sync.
When to Use
Use when:
- Setting up Colyseus server
- Creating room handlers
- Implementing matchmaking
- Configuring server transport
Server Setup (ESM Required)
import { Server } from 'colyseus';
import { createServer } from 'http';
import express from 'express';
import { WebSocketTransport } from '@colyseus/ws-transport';
import { GameRoom } from './rooms/GameRoom';
const port = Number(process.env.PORT) || 2567;
const app = express();
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
next();
});
const httpServer = createServer(app);
const gameServer = new Server({
transport: new WebSocketTransport({ server: httpServer }),
});
gameServer.define('game_room', GameRoom);
gameServer.listen(port);
console.log(`Colyseus server listening on wss://localhost:${port}`);
CRITICAL: Server MUST use "type": "module" in package.json. Do NOT use CommonJS.
Room Handler Definition
import { Room, Client } from 'colyseus';
import { Schema, type, MapSchema } from '@colyseus/schema';
export class GameRoom extends Room<GameRoomState> {
onCreate(options: any) {
this.setState(new GameRoomState());
console.log(`[GameRoom] Created: ${this.roomId}`);
this.setSimulationInterval((deltaTime) => {
this.update(deltaTime);
});
this.clock.setTimeout(() => this.endMatch(), 5000);
this.clock.setInterval(() => this.(), );
}
() {
player = ();
player. = client.;
...(client., player);
client.(, { : client. });
}
() {
...(client.);
}
() {
(data.) {
:
.(client, data);
;
}
}
() {
.();
}
}
Message Broadcasting
this.broadcast('game_event', { data: 'value' });
client.send('personal_event', { data: 'value' });
this.broadcast('game_event', { data: 'value' }, [client.sessionId]);
Server Definition Options
gameServer
.define('battle', BattleRoom)
.filterBy(['mode', 'map']);
gameServer
.define('battle', BattleRoom)
.sortBy({ clients: -1 });
gameServer
.define('battle', BattleRoom)
.enableRealtimeListing();
gameServer
.define('chat', ChatRoom)
.on('create', (room) => console.log('Room created'))
.on('dispose', (room) => console.log('Room disposed'))
.on('join', (room, client) => console.log('Client joined'))
.on('leave', (room, client) => console.());
Transport Configuration
import { WebSocketTransport } from '@colyseus/ws-transport';
new WebSocketTransport({
server,
pingInterval: 30000,
pingMaxRetries: 3,
});
if (process.env.NODE_ENV !== 'production') {
gameServer.simulateLatency(200);
}
Scaling with Redis
import { Server, RedisPresence, RedisDriver } from 'colyseus';
const gameServer = new Server({
presence: new RedisPresence(),
driver: new RedisDriver(),
});
Best Practices
- Always use ESM -
"type": "module" in package.json
- Validate all inputs - Never trust client data
- Rate limit actions - Prevent spam exploits
- Log room lifecycle - For debugging
- Use Schema for state - Efficient binary serialization
Common Mistakes
| ❌ Wrong | ✅ Right |
|---|
require/module.exports | import/export with "type": "module" |
Server using colyseus.js | Server uses colyseus package |
| Trusting client positions | Validate all inputs server-side |
Reference
Room Lifecycle Best Practices (Updated 2026-01-28)
From arch-003 retrospective - proven patterns for Colyseus room implementation.
Lifecycle Event Order
onCreate (once) → onAuth (per client) → onJoin (per client) → onMessage (loop)
→ onLeave (per client) → onDispose (once, when no clients)
onCreate Implementation Pattern
import { Room, Client } from 'colyseus';
import { MyRoomState } from '../schema/MyRoomState';
export class MyRoom extends Room<MyRoomState> {
maxClients = 64;
onCreate(options: any) {
this.setState(new MyRoomState());
this.setPatchRate(50);
this.setSimulationInterval((deltaTime) => {
this.gameLoop(deltaTime);
});
this.onMessage('input', (client, data) => {
this.handlePlayerInput(client, data);
});
console.log(, options);
}
(: , : ): | <> {
;
}
() {
.();
player = ();
player. = client.;
...(client., player);
client.(, { : client. });
}
() {
.();
...(client.);
}
() {
.();
}
() {
}
}
Room Configuration Best Practices
| Configuration | Value | Purpose |
|---|
maxClients | 64 | FFA mode capacity |
patchRate | 50ms | 20Hz state sync for multiplayer |
autoDispose | true | Cleanup when empty (default) |
setSimulationInterval | 16.6ms | 60Hz game loop (optional) |
Message Handler Pattern
this.onMessage('move', (client, data) => {
const player = this.state.players.get(client.sessionId);
if (player) {
player.x = data.x;
player.y = data.y;
}
});
this.onMessage('*', (client, type, data) => {
console.log(`Unhandled message: ${type}`, data);
});
Sources: