NodeJS + Colyseus
Build multiplayer NodeJS game servers using Colyseus and deploy them on GameFlow.
Getting Started
Dependencies
Install Colyseus and the Agones SDK:
npm install colyseus @colyseus/tools @colyseus/schema @google-cloud/agones-sdk express
Project Structure
game-server/
src/
index.ts # Entry point, starts server, reads payload, pre-creates room
app.config.ts # Colyseus server definition and room registration
agones.ts # Agones SDK wrapper (connect, ready, health, shutdown, payload)
types.ts # Agones SDK interface
rooms/
room.ts # Game room logic (Colyseus Room)
schema/
state.ts # Networked state (Colyseus Schema)
types.ts # Room option types
Colyseus Room Definition
Register your room in the Colyseus server config:
// app.config.ts
import { defineServer, defineRoom } from "colyseus";
import { PongRoom } from "./rooms/room.js";
const server = defineServer({
rooms: {
pong_room: defineRoom(PongRoom),
},
});
export default server;
Agones SDK Integration
Wrap the Agones SDK to manage lifecycle events, connect, ready, health pings, shutdown, and reading the GameFlow payload.
Initialize & Health
// agones.ts
import AgonesSDKModule from "@google-cloud/agones-sdk";
const AgonesSDK = (AgonesSDKModule as any).default ?? AgonesSDKModule;
let agonesSDK: any = null;
let healthInterval: ReturnType<typeof setInterval> | null = null;
export async function initAgones() {
agonesSDK = new AgonesSDK();
await agonesSDK.connect();
}
export async function reportReadyAndHealth() {
if (!agonesSDK) return;
healthInterval = setInterval(() => {
agonesSDK.health((err: unknown) => {
if (err) console.error("[Agones] health ping failed:", err);
});
}, 2_000);
await agonesSDK.ready();
}
Shutdown
export async function shutdownAgones() {
if (!agonesSDK) return;
if (healthInterval) clearInterval(healthInterval);
await agonesSDK.shutdown();
}
Reading the GameFlow Payload
export async function getGameFlowPayload<T = unknown>(): Promise<T | null> {
if (!agonesSDK) return null;
const gs = await agonesSDK.getGameServer();
const annotations = Object.fromEntries(gs.objectMeta?.annotationsMap ?? []);
const raw = annotations["GAMEFLOW_PAYLOAD"];
if (!raw) return null;
return JSON.parse(raw) as T;
}
Server Entry Point
Start Colyseus, read the GameFlow payload, pre-create the room, then signal Agones ready:
// index.ts
import app from "./app.config.js";
import { matchMaker } from "colyseus";
import { listen } from "@colyseus/tools";
import { initAgones, reportReadyAndHealth, getGameFlowPayload } from "./agones.js";
interface AllocationPayload {
players: string[];
teamA: { accountId: string; username: string }[];
teamB: { accountId: string; username: string }[];
}
listen(app).then(async () => {
if (process.env.AGONES_ENABLED === "true") {
await initAgones();
}
let roomOptions: Record<string, unknown> = {};
if (process.env.AGONES_ENABLED === "true") {
const payload = await getGameFlowPayload<AllocationPayload>();
if (payload) {
roomOptions = { teamA: payload.teamA, teamB: payload.teamB };
}
}
const roomCache = await matchMaker.createRoom("pong_room", roomOptions);
console.log("Room pre-created:", JSON.stringify(roomCache));
if (process.env.AGONES_ENABLED === "true") {
await reportReadyAndHealth();
}
});
Room Lifecycle
// rooms/room.ts
import { Room, Client } from "colyseus";
import { shutdownAgones } from "../agones.js";
export class PongRoom extends Room {
autoDispose = false;
onCreate(options: RoomOptions) {
if (options?.teamA) { /* assign team A slots */ }
if (options?.teamB) { /* assign team B slots */ }
}
onJoin(client: Client, options: JoinOptions) {}
onLeave(client: Client) {
if (this.realPlayersRemaining === 0) {
this.disconnect();
}
}
async onDispose() {
await shutdownAgones();
}
}
Deploying to GameFlow
Create and Configure a Game
- Create a new game on the GameFlow Dashboard
- Set Memory to
512MB, vCPU to0.5 cores - Set the Port to
2567 - Enable the us-east region
Prepare Your Build
Create a server.zip file containing the following files and folders from your project:
src/, your server source codepackage.jsonpackage-lock.json.env.productionecosystem.config.cjs
zip -r server.zip src/ package.json package-lock.json .env.production ecosystem.config.cjs
Deploy and Test
- Upload
server.zipin the builds section - Click Create Test Server, wait for it to reach "Ready"
- Connect your client:
new Client('ws://<server-ip>:2567')
Full Example
A complete working example (Colyseus + GameFlow + Agones) is available here: