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 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

  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) is available here:

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