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”Use the standard zenstack CLI with the --schema flag to target each connection’s schema independently.
Migrations
Section titled “Migrations”# Run dev migrations for the main connectionzenstack migrate dev --schema db/main/schema.zmodel --name add_users_table
# Deploy migrations to productionzenstack migrate deploy --schema db/main/schema.zmodel
# Check migration statuszenstack migrate status --schema db/main/schema.zmodel
# Reset databasezenstack migrate reset --schema db/main/schema.zmodelSchema push
Section titled “Schema push”# Push schema to database (skips migration history)zenstack db push --schema db/main/schema.zmodel
# Push analytics schemazenstack db push --schema db/analytics/schema.zmodelCommon options
Section titled “Common options”| Option | Description |
|---|---|
--schema <path> | Path to the connection’s .zmodel schema file |
--name <name> | Migration name (for migrate dev) |
Next steps
Section titled “Next steps”- Database Events for reacting to database operations.
- Factories for generating test data.
- Seeders for populating the database.