Database

Database

The recommended way to persist game data in Colyseus is @colyseus/database, the official persistence layer, built on Drizzle ORM (1.0.0-rc). 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 management
  • db.saves: versioned cloud saves with optimistic locking
  • db.leaderboards: keep-best scores, seasons, top-N and around-me queries
  • db.configs: typed, hot-reloadable live-ops configuration
  • db.analytics / db.moderation / db.notes: event tracking, roles, and player notes
  • db.segments: declarative player cohorts for targeting
  • DatabaseDriver: 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/database

For PostgreSQL, also install the postgres driver:

npm install --save postgres

For embedded PGlite, install @electric-sql/pglite instead:

npm install --save @electric-sql/pglite

Drizzle version

@colyseus/database is built on the 1.0 release candidate of Drizzle ORM. Whenever you import Drizzle yourself (column builders for custom tables, eq in queries, drizzle-kit for migrations), install it from the rc dist-tag. 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@rc

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.

src/app.config.ts
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:

src/rooms/MyRoom.ts
import { Room } from "colyseus";
import { db } from "../app.config";
 
export class MyRoom extends Room {
    async onJoin(client, options) {
        // 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 stringDialectNotes
(omitted) or ./game.dbSQLiteFile-based; great for development
:memory:SQLiteEphemeral; useful for tests
postgres://… / postgresql://…PostgreSQLProduction; requires the postgres package
pglite://./data / pglite://:memory:PGliteEmbedded Postgres, no external server
src/app.config.ts
// 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" });

Migrations

The migrations option controls how the schema is applied at boot:

  • "auto" (default): creates missing tables with their primary keys, foreign keys, UNIQUE and CHECK constraints, 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. a drizzle-kit migrate step in CI before the server starts).
  • { files: "./drizzle" }: runs SQL migration files generated by drizzle-kit. Drizzle tracks applied migrations, so reruns are safe. See Migrations with drizzle-kit.
src/app.config.ts
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):

src/app.config.ts
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/auth integration (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/onLeave integration patterns.