Database
@stratal/framework provides a database module built on ZenStack ORM. It supports multiple named connections, per-connection schemas, and plugins for error handling, event emission, and schema switching.
Install dependencies
Section titled “Install dependencies”yarn add @stratal/frameworkConfigure DatabaseModule
Section titled “Configure DatabaseModule”Use DatabaseModule.forRoot() to configure your database connections:
import { Module } from 'stratal/module'import { DatabaseModule } from '@stratal/framework/database'import { schema } from '../db/schema'
@Module({ imports: [ DatabaseModule.forRoot({ default: 'main', connections: [ { name: 'main', schema, dialect: () => new PostgresDialect({ pool }), }, ], }), ],})export class AppModule {}Configuration options
Section titled “Configuration options”| Option | Type | Description |
|---|---|---|
default | string | Name of the default connection |
connections | DatabaseConnectionConfig[] | Array of connection configurations |
Each connection accepts:
| Option | Type | Description |
|---|---|---|
name | string | Unique connection name |
schema | object | Your ZenStack schema for this connection |
dialect | () => Dialect | Factory function returning a database dialect |
plugins | Plugin[] | Optional array of database plugins |
Injecting the database
Section titled “Injecting the database”Default connection
Section titled “Default connection”Inject the default database connection using DI_TOKENS.Database:
import { Transient, inject, DI_TOKENS } from 'stratal/di'import type { DatabaseService } from '@stratal/framework/database'
@Transient()export class UsersService { constructor( @inject(DI_TOKENS.Database) private readonly db: DatabaseService, ) {}
async findAll() { return this.db.user.findMany() }}Named connections
Section titled “Named connections”Use @InjectDB(name) to inject a specific named connection:
import { Transient } from 'stratal/di'import { InjectDB, type DatabaseService } from '@stratal/framework/database'
@Transient()export class AnalyticsService { constructor( @InjectDB('analytics') private readonly db: DatabaseService<'analytics'>, ) {}
async trackEvent(event: string) { await this.db.analyticsEvent.create({ data: { event } }) }}Multi-connection support
Section titled “Multi-connection support”Configure multiple connections by providing each with its own schema. Each connection has an independent .zmodel file containing only the models for that connection.
Directory structure
Section titled “Directory structure”db/├── main/│ └── schema.zmodel└── analytics/ └── schema.zmodelConfiguration
Section titled “Configuration”import { Module } from 'stratal/module'import { DatabaseModule } from '@stratal/framework/database'import { schema as mainSchema } from '../db/main/schema'import { schema as analyticsSchema } from '../db/analytics/schema'
@Module({ imports: [ DatabaseModule.forRoot({ default: 'main', connections: [ { name: 'main', schema: mainSchema, dialect: () => new PostgresDialect({ pool: mainPool }), }, { name: 'analytics', schema: analyticsSchema, dialect: () => new PostgresDialect({ pool: analyticsPool }), }, ], }), ],})export class AppModule {}With per-connection schemas, DatabaseService<'main'> only exposes models defined in the main schema, while DatabaseService<'analytics'> only exposes models defined in the analytics schema.
Per-connection schemas
Section titled “Per-connection schemas”Each connection has its own independent .zmodel file — simply define each schema separately.
db/main/schema.zmodel
datasource db { provider = "postgresql" url = env("MAIN_DATABASE_URL")}
model User { id String @id @default(cuid()) email String @unique posts Post[]}
model Post { id String @id @default(cuid()) title String author User @relation(fields: [authorId], references: [id]) authorId String}db/analytics/schema.zmodel
datasource db { provider = "postgresql" url = env("ANALYTICS_DATABASE_URL")}
model AnalyticsEvent { id String @id @default(cuid()) event String createdAt DateTime @default(now())}
model PageView { id String @id @default(cuid()) path String}Plugins
Section titled “Plugins”The DatabaseModule automatically registers the following plugins on every connection — no configuration needed:
- ErrorHandlerPlugin — Transforms ZenStack errors into Stratal
ApplicationErrorinstances with appropriate HTTP status codes. - EventEmitterPlugin — Emits events before and after database operations, integrating with Stratal’s event system. See Database Events for the full event pattern documentation.
SchemaSwitcherPlugin (opt-in)
Section titled “SchemaSwitcherPlugin (opt-in)”The only user-configurable plugin is SchemaSwitcherPlugin, which sets the PostgreSQL search_path for multi-tenant isolation:
import { SchemaSwitcherPlugin } from '@stratal/framework/database'
{ name: 'main', schema: mainSchema, dialect: () => new PostgresDialect({ pool }), plugins: [new SchemaSwitcherPlugin()],}Type-safe schema augmentation
Section titled “Type-safe schema augmentation”To get full type safety across connections, augment the StratalDatabase interface with your per-connection schema types:
import type { MainSchemaType } from '../db/main/schema'import type { AnalyticsSchemaType } from '../db/analytics/schema'
declare module '@stratal/framework/database' { interface StratalDatabase { schemas: { main: MainSchemaType analytics: AnalyticsSchemaType } defaultConnection: 'main' }}This provides full type safety — DatabaseService returns a client typed with the default connection’s schema, while DatabaseService<'analytics'> returns a client typed with the analytics schema.
CLI commands
Section titled “CLI commands”@stratal/framework registers database and migration commands with the Quarry CLI. Use the --schema flag to target each connection’s schema independently.
Database commands
Section titled “Database commands”# Generate ZenStack ORM clientnpx quarry db:generate
# Generate with watch mode for developmentnpx quarry db:generate --watch
# Introspect an existing database and generate schemanpx quarry db:pull --schema db/main/schema.zmodel
# Push schema changes to the database (skips migration history)npx quarry db:push --schema db/main/schema.zmodel| Command | Options | Description |
|---|---|---|
db:generate | --schema, --watch | Generate ZenStack ORM client |
db:pull | --schema | Introspect database and generate schema |
db:push | --schema, --accept-data-loss, --force-reset | Push schema changes to database |
Migration commands
Section titled “Migration commands”# Create and apply a migrationnpx quarry migrate:dev --schema db/main/schema.zmodel --name add_users_table
# Create migration without applyingnpx quarry migrate:dev --schema db/main/schema.zmodel --name add_users_table --create-only
# Deploy pending migrations to productionnpx quarry migrate:deploy --schema db/main/schema.zmodel
# Check migration statusnpx quarry migrate:status --schema db/main/schema.zmodel
# Reset database (destructive)npx quarry migrate:reset --schema db/main/schema.zmodel --force| Command | Options | Description |
|---|---|---|
migrate:dev | --schema, --name, --create-only | Create and apply a migration |
migrate:deploy | --schema | Deploy pending migrations |
migrate:status | --schema | Check migration status |
migrate:reset | --schema, --force, --skip-seed | Reset database |
Next steps
Section titled “Next steps”- Database Events for reacting to database operations.
- Factories for generating test data.
- Seeders for populating the database.