Database
The recommended way to persist game data in Colyseus is @colyseus/database, the official persistence layer built on version 1.0.0-rc of Drizzle ORM. A single typed connection backs player accounts, cloud saves, leaderboards, live configs, analytics, moderation, and matchmaking.
A single GameDatabase instance exposes:
db.auth: user store for@colyseus/auth: bans, session revocation, password managementdb.saves: versioned cloud saves with optimistic lockingdb.leaderboards: keep-best scores, seasons, top-N and around-me queriesdb.configs: typed, hot-reloadable live-ops configurationdb.analytics/db.moderation/db.notes: event tracking, roles, and player notesdb.segments: declarative player cohorts for targetingDatabaseDriver: a matchmaking driver reusing the same connection
Colyseus itself remains database-agnostic: if your project needs a different stack, bring your own database and query it directly from your rooms.
@colyseus/database was introduced in Colyseus 0.18. Feedback is welcome on the public roadmap.
Installation
npm install --save @colyseus/databaseFor PostgreSQL, also install postgres (postgres-js):
npm install --save postgresA postgres:// connection string always opens postgres-js, never pg (node-postgres). Drizzle’s own PostgreSQL guide installs pg, so check which one you have. To keep pg, open the pool yourself and pass it as db.
For embedded PGlite, install @electric-sql/pglite instead:
npm install --save @electric-sql/pgliteDrizzle version
@colyseus/database is built on the 1.0 release candidate of Drizzle ORM and declares drizzle-orm as a peer dependency. Install it from the rc dist-tag, alongside drizzle-kit if you generate migrations. npm’s latest is still the 0.x line, and its column builders fail inside the table factory with colBuilder.build(...).postBuild is not a function.
npm install --save drizzle-orm@rc
npm install --save-dev drizzle-kit@rcKeep a single copy of drizzle-orm in node_modules. With two copies, the code still runs. tsc then rejects eq(db.tables.users.id, …) and every other place where a column from one copy meets a query builder from the other. npm ls drizzle-orm shows any duplicate.
Quick start
Construct a GameDatabase and pass it to defineServer via the database option. The server boots the database (running migrations) before it starts listening, and automatically mounts the @colyseus/auth routes when that package is installed.
import { defineServer } from "colyseus";
import { GameDatabase } from "@colyseus/database";
export const db = new GameDatabase({
connectionString: process.env.DATABASE_URL,
});
export default defineServer({
database: db,
// ...rooms, routes, etc.
});Export the db instance so your rooms can import it. Inside any room lifecycle method, the services are ready to use:
import { Room } from "colyseus";
import { db } from "../app.config";
export class MyRoom extends Room {
async onJoin(client) {
// client.auth was populated by the default static onAuth,
// which already rejects banned/revoked tokens automatically.
await db.saves.load(client.auth.id);
}
}Dialects & connection strings
The dialect is auto-detected from the connection string. When omitted, SQLite is used with the default file colyseus.db.
| Connection string | Dialect | Notes |
|---|---|---|
(omitted) or ./game.db | SQLite | File-based; great for development |
:memory: | SQLite | Ephemeral; useful for tests |
postgres://… / postgresql://… | PostgreSQL | Production; requires the postgres package (postgres-js, not pg) |
pglite://./data / pglite://:memory: | PGlite | Embedded Postgres, no external server |
// SQLite (development): defaults to colyseus.db
new GameDatabase();
// PostgreSQL (production)
new GameDatabase({ connectionString: process.env.DATABASE_URL });
// Embedded PGlite, file-backed
new GameDatabase({ dialect: "pglite", connectionString: "pglite://./data" });Use an existing Drizzle instance
Pass db to reuse a Drizzle instance you already opened. Use this option to run on pg (node-postgres), or to share one pool with the rest of your app. connectionString is ignored, and the dialect is read from the instance unless you pass dialect.
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { GameDatabase } from "@colyseus/database";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = new GameDatabase({
db: drizzle({ client: pool }),
});migrations: "auto" runs through the instance’s own client, so any driver Drizzle supports works: postgres-js, node-postgres, PGlite, or node:sqlite. An instance that exposes no client (an HTTP proxy driver, for example) needs migrations: "skip" or { files }. The connection stays yours: shutdown() closes only connections the package opened itself.
Migrations
The migrations option controls how the schema is applied at boot:
"auto"(default): creates missing tables with their primary keys, foreign keys,UNIQUEandCHECKconstraints, adds missing columns to existing tables, and creates missing indexes on every boot. Idempotent and convenient for development. It never drops a column, changes a type, or adds a constraint to a table that already exists. See Constraints and indexes."skip": does nothing; you manage the schema externally (e.g. adrizzle-kit migratestep in CI before the server starts).{ files: "./drizzle" }: runs SQL migration files generated bydrizzle-kit. Drizzle tracks applied migrations, so reruns are safe. See Migrations with drizzle-kit.
new GameDatabase({
connectionString: process.env.DATABASE_URL,
migrations: "skip", // schema applied by CI
});Matchmaking driver
Reuse the same connection as your matchmaking driver (one pool for everything, no separate Redis required):
import { defineServer } from "colyseus";
import { GameDatabase, DatabaseDriver } from "@colyseus/database";
export const db = new GameDatabase({ connectionString: process.env.DATABASE_URL });
export default defineServer({
database: db,
driver: new DatabaseDriver(), // adopts the GameDatabase connection
});Without options, the driver binds to the most recently constructed GameDatabase. With more than one instance in the process, pass it explicitly: new DatabaseDriver({ database: db }). Custom room-cache columns and the standalone (no GameDatabase) mode are covered on the Driver page.
Next steps
- Authentication: the built-in
@colyseus/authintegration (auto-mounted routes, ban gating, JWT revocation, admin helpers). - Services: cloud saves, leaderboards, live configs, analytics, moderation, notes, and the matching room plugins.
- Customizing built-in table schemas: extend the package’s drizzle tables with your own columns, constraints, and indexes.
- Bring your own database: ORMs, query builders, Firebase, and the
onAuth/onLeaveintegration patterns.