Database
This guide covers the zumito-db ORM — a multi-driver, decorator-based database abstraction layer for Zumito Framework.
Overview
Section titled “Overview”zumito-db provides a unified API for MongoDB, SQLite, PostgreSQL, MySQL, and TingoDB. Define your models with decorators and access them through a Repository pattern or a fluent QueryBuilder.
Defining models
Section titled “Defining models”Use @Collection and @Field decorators to define models.
Placing models inside a module
Section titled “Placing models inside a module”The recommended way is to place model files inside your module’s models/ folder — they are auto-loaded and auto-registered by the framework on startup:
src/modules/guild-settings/├── commands/├── events/├── models/ ← Models live here│ ├── Guild.ts│ └── Member.ts├── translations/└── index.tsEach model file exports one or more classes decorated with @Collection:
import { Collection, Field } from 'zumito-framework';
@Collection({ name: 'guilds' })export class Guild { @Field({ type: 'string', primary: true, unique: true }) guild_id: string;
@Field({ type: 'string', default: 'en' }) lang: string;}The framework automatically:
- Scans
models/in each module duringmodule.initialize() - Registers every class with
@Collectionmetadata into theDatabaseManager - Creates/ensures the collection/table schema on startup via
db.ensureSchemas()
No manual registration needed — just drop the file in models/ and export the class.
Manual registration (alternative)
Section titled “Manual registration (alternative)”If you prefer to keep models outside the models/ folder, register them manually in zumito.config.ts:
import { Guild } from './src/modules/guild-settings/models/Guild';
export const config: LauncherConfig = { models: [Guild],};Model definition reference
Section titled “Model definition reference”import { Collection, Field, HasMany } from 'zumito-framework';
@Collection({ name: 'guilds' })export class Guild { @Field({ type: 'string', primary: true, unique: true }) guild_id: string;
@Field({ type: 'string', default: 'en' }) lang: string;
@Field({ type: 'string', default: 'z-' })
// ... rest of existing content prefix: string;
@Field({ type: 'boolean', default: false }) public: boolean;}
@Collection({ name: 'members' })export class Member { @Field({ type: 'string', primary: true }) id: string;
@Field({ type: 'string' }) name: string;}Field options
Section titled “Field options”| Option | Type | Default | Description |
|---|---|---|---|
type | 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array' | inferred from TS type | Field data type |
primary | boolean | false | Marks the field as primary key |
unique | boolean | false | Creates a unique index |
nullable | boolean | true | Allows null values |
default | any | undefined | Default value for new documents |
Relations
Section titled “Relations”Define relationships between models:
import { Collection, Field, HasMany, BelongsTo, HasOne } from 'zumito-framework';
@Collection({ name: 'users' })export class User { @Field({ type: 'string', primary: true }) id: string;
@HasMany(() => Post, { foreignKey: 'userId' }) posts: Post[];
@HasOne(() => Profile, { foreignKey: 'userId' }) profile: Profile;}
@Collection({ name: 'posts' })export class Post { @Field({ type: 'string', primary: true }) id: string;
@Field({ type: 'string' }) userId: string;
@BelongsTo(() => User, { foreignKey: 'userId' }) author: User;}Accessing the database
Section titled “Accessing the database”Via ServiceContainer
Section titled “Via ServiceContainer”import { DatabaseManager, ServiceContainer } from 'zumito-framework';
const db = ServiceContainer.get(DatabaseManager) as DatabaseManager;Via framework instance
Section titled “Via framework instance”// In commands: parameters.framework.dbconst guilds = framework.db.getRepository(Guild);Repository pattern
Section titled “Repository pattern”Get a type-safe repository for any model:
const guilds = db.getRepository(Guild);
// Find allconst all = await guilds.find();
// Find with conditionsconst guild = await guilds.findOne({ guild_id: '123456' });
// Insertawait guilds.insert({ guild_id: '789', lang: 'es' });
// Updateawait guilds.update({ guild_id: '789' }, { lang: 'fr' });
// Deleteawait guilds.delete({ guild_id: '789' });
// Countconst total = await guilds.count({ lang: 'en' });QueryBuilder
Section titled “QueryBuilder”For complex queries, use the fluent QueryBuilder:
const results = await guilds .query() .where('lang', 'eq', 'es') .orWhere('lang', 'eq', 'en') .sort('guild_id', 'asc') .limit(10) .select(['guild_id', 'prefix']) .exec();Available query methods
Section titled “Available query methods”| Method | Description |
|---|---|
.where(field, operator, value) | Adds AND condition |
.andWhere(field, operator, value) | Same as .where() |
.orWhere(field, operator, value) | Adds OR condition |
.select([...fields]) | Projects specific fields |
.sort(field, 'asc' | 'desc') | Orders results |
.limit(n) | Limits results |
.offset(n) | Skips first n results |
.exec() | Executes and returns array |
.first() | Returns first match or null |
.count() | Returns count |
.populate('relation') | Loads related data |
Operators
Section titled “Operators”| Operator | Description | SQL | MongoDB |
|---|---|---|---|
eq | Equals | = ? | { $eq } |
neq | Not equals | != ? | { $ne } |
gt | Greater than | > ? | { $gt } |
gte | Greater or equal | >= ? | { $gte } |
lt | Less than | < ? | { $lt } |
lte | Less or equal | <= ? | { $lte } |
in | In array | IN (...) | { $in } |
nin | Not in array | NOT IN (...) | { $nin } |
like | String contains | LIKE ? | { $regex } |
between | Between range | BETWEEN | { $gte, $lte } |
Configuration
Section titled “Configuration”Configure the database in zumito.config.ts. The framework selects the driver based on database.default. If omitted, it falls back to MONGO_URI / mongoQueryString for backward compatibility.
Development (memory — no setup)
Section titled “Development (memory — no setup)”import type { LauncherConfig } from 'zumito-framework';
export const config: LauncherConfig = { database: { default: 'memory', drivers: { memory: {} }, },};Zero dependencies, data lives in memory, resets on restart. Ideal for quick iteration.
Development (tingo — file-based)
Section titled “Development (tingo — file-based)”database: { default: 'tingo', drivers: { tingo: { path: './db/tingodb' } },},MongoDB-compatible API, stores data as files locally. No server needed.
Development (sqlite — file-based SQL)
Section titled “Development (sqlite — file-based SQL)”database: { default: 'sqlite', drivers: { sqlite: { filename: './dev.db' } },},Requires npm install better-sqlite3. Single-file SQL database.
Production (MongoDB)
Section titled “Production (MongoDB)”import type { LauncherConfig } from 'zumito-framework';
export const config: LauncherConfig = { database: { default: 'mongo', drivers: { mongo: { uri: process.env.MONGO_URI! }, }, },};Switching by environment
Section titled “Switching by environment”database: { default: process.env.DATABASE_TYPE || 'memory', drivers: { memory: {}, mongo: { uri: process.env.MONGO_URI! }, tingo: { path: './db/tingodb' }, },},Then set DATABASE_TYPE=memory in .env for dev, DATABASE_TYPE=mongo for prod.
Registering models
Section titled “Registering models”Models placed in a module’s models/ folder are auto-loaded. To also register models globally, list them in models:
export const config: LauncherConfig = { models: [Guild, Member],};Auto-registered models have their schemas ensured on startup (collections/tables created, indexes applied).
Migrations (optional)
Section titled “Migrations (optional)”Define migration classes for versioned schema changes:
import { Migration, DatabaseManager } from 'zumito-framework';
export class CreateProductsTable extends Migration { name = '20240601_create_products';
async up(db: DatabaseManager) { const driver = db.getDriver(); await driver.ensureSchema({ name: 'Product', collection: 'products', fields: [ { name: 'id', type: 'string', primary: true, unique: false, nullable: false, propertyKey: 'id' }, { name: 'name', type: 'string', primary: false, unique: false, nullable: false, propertyKey: 'name' }, ], relations: [], target: null, }); }
async down(db: DatabaseManager) { await db.dropCollection('products'); }}Register migrations in your config:
export const config: LauncherConfig = { database: { migrations: [new CreateProductsTable()], },};Migrations run automatically on startup via db.migrator.latest().
Migration API
Section titled “Migration API”// Run pending migrationsawait db.migrator.latest(migrations);
// Rollback last migrationawait db.migrator.rollback(migrations);
// Check statusconst status = await db.migrator.status(migrations);Drivers
Section titled “Drivers”zumito-db loads drivers on demand — only the driver you use is imported at runtime.
| Driver | Config key | Dependency | Use case |
|---|---|---|---|
| MongoDB | mongo | mongodb (included) | Production, Discord bot data |
| SQLite | sqlite | better-sqlite3 (optional) | Local dev, embedded SQL |
| TingoDB | tingo | tingodb (optional) | Local dev, MongoDB-compatible files |
| Memory | memory | (built-in) | Testing, ephemeral |
Testing with MemoryDriver
Section titled “Testing with MemoryDriver”The MemoryDriver provides an in-memory database for tests:
import { createTestFramework } from 'zumito-framework/testing';import { Collection, Field, DatabaseManager } from 'zumito-framework';
const { db } = createTestFramework();
@Collection({ name: 'users' })class User { @Field({ type: 'string', primary: true }) id: string;}
// db is a fully mocked DatabaseManager// Override specific methods:db.getRepository = vi.fn().mockReturnValue({ findOne: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),});Direct driver access
Section titled “Direct driver access”For operations not covered by the Repository or QueryBuilder:
const driver = db.getDriver();
// MongoDB raw accessawait driver.raw.collection('guilds').aggregate([...]).toArray();
// SQLite raw accessdriver.raw.exec('SELECT * FROM guilds WHERE lang = ?', 'es');Migrating from legacy MongoDB
Section titled “Migrating from legacy MongoDB”If your project used the old framework.database or MongoService:
// Old (deprecated - still works but shows warnings)const db = ServiceContainer.get(Db);await db.collection('guilds').findOne({ guild_id: '123' });
// New — define a model first, then use Repositoryimport { Collection, Field, DatabaseManager } from 'zumito-framework';
@Collection({ name: 'guilds' })class Guild { @Field({ type: 'string', primary: true }) guild_id: string;}
const db = ServiceContainer.get(DatabaseManager) as DatabaseManager;const guilds = db.getRepository(Guild);await guilds.findOne({ guild_id: '123' });The old framework.database and framework.mongoService are @deprecated and will be removed in a future major version.