Skip to main content

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

  1. Create a new game on the GameFlow Dashboard
  2. Set Memory to 512MB, vCPU to 0.5 cores
  3. Set the Port to 2567
  4. 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 code
  • package.json
  • package-lock.json
  • .env.production
  • ecosystem.config.cjs
zip -r server.zip src/ package.json package-lock.json .env.production ecosystem.config.cjs

Deploy and Test

  1. Upload server.zip in the builds section
  2. Click Create Test Server, wait for it to reach "Ready"
  3. Connect your client: new Client('ws://<server-ip>:2567')

Full Example

A complete working example (Colyseus + GameFlow + Agones) is available here:

https://github.com/GameFlowGG/colyseus-multiplayer-example