NodeJS + Colyseus
Build multiplayer NodeJS game servers using Colyseus and deploy them on GameFlow.
Getting Started
Dependencies
Install Colyseus and the GameFlow Game Server SDK:
npm install colyseus @colyseus/tools @colyseus/schema @gameflow.gg/gameserver-sdk express
Project Structure
game-server/
src/
index.ts # Entry point: connect, read payload, pre-create room, signal ready
app.config.ts # Colyseus server definition and room registration
gameflow.ts # Shared GameFlow SDK client + typed launch payload
rooms/
room.ts # Game room logic (tracks players, shuts down on dispose)
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;
GameFlow SDK Integration
The SDK handles the entire server lifecycle: connect, ready, automatic health reporting, player tracking, and shutdown. There is no health() loop to write and no environment gating: the SDK connects to GameFlow in production and falls back to local mode automatically when you run the same build on your machine.
Create one shared client for the process and expose a typed helper for the launch payload:
// gameflow.ts (ESM module; top-level await connects once)
import { GameFlow } from "@gameflow.gg/gameserver-sdk";
export const gf = await GameFlow.connect();
export interface AllocationPayload {
players: string[];
teamA: { accountId: string; username: string }[];
teamB: { accountId: string; username: string }[];
}
export async function getPayload(): Promise<AllocationPayload | null> {
const raw = await gf.payload(); // string | undefined
return raw ? (JSON.parse(raw) as AllocationPayload) : null;
}
Server Entry Point
Read the GameFlow payload, pre-create the room, then signal readiness. Health reporting starts automatically once ready() resolves:
// index.ts
import app from "./app.config.js";
import { matchMaker } from "colyseus";
import { listen } from "@colyseus/tools";
import { gf, getPayload } from "./gameflow.js";
listen(app).then(async () => {
// Launch payload GameFlow passed at allocation (null in local dev).
const payload = await getPayload();
const roomOptions = payload ? { teamA: payload.teamA, teamB: payload.teamB } : {};
const roomCache = await matchMaker.createRoom("pong_room", roomOptions);
console.log("Room pre-created:", JSON.stringify(roomCache));
await gf.ready(); // server is now accepting players; health pings start
});
Room Lifecycle
Track every player as they join and leave. This keeps the server alive (GameFlow reaps servers reporting zero players past the idle timeout) and powers the live counts in the dashboard. Shut down cleanly when the room disposes:
// rooms/room.ts
import { Room, Client } from "colyseus";
import { gf } from "../gameflow.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 */ }
}
async onJoin(client: Client) {
await gf.players.connect(client.sessionId);
}
async onLeave(client: Client) {
await gf.players.disconnect(client.sessionId);
if (this.realPlayersRemaining === 0) {
this.disconnect();
}
}
async onDispose() {
await gf.shutdown();
}
}
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) is available here: