DataSources
A DataSource manages database connections and supports schema auto-discovery from repositories.
Connectors
This guide covers the PostgreSQL connector (BasePostgresDataSource, aliased as BaseDataSource for backward compatibility). It's the primary relational engine and the one used by most applications. IGNIS also ships a SQLite connector (BaseSqliteDataSource, see SQLite) and a typesense connector for full-text/vector search (see Search & Typesense). All three implement the same engine-neutral AbstractDataSource contract - see Connectors for the architecture.
Creating a DataSource
// src/datasources/postgres.datasource.ts
import {
BasePostgresDataSource,
datasource,
ValueOrPromise,
} from '@venizia/ignis';
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
import { Pool } from 'pg';
interface IDSConfigs {
host: string;
port: number;
database: string;
user: string;
password: string;
}
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
constructor() {
super({
name: PostgresDataSource.name,
config: {
host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'mydb',
user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
},
// No schema needed - auto-discovered from @repository bindings!
});
}
override configure(): ValueOrPromise<void> {
const schema = Object.keys(this.getSchema());
this.logger.debug('[configure] Auto-discovered schema | Keys: %o', schema);
// That is all - naming NodePostgresDriver above is what wires the driver and connector.
this.client = new Pool(this.settings);
}
override getConnectionString(): ValueOrPromise<string> {
const { host, port, user, password, database } = this.settings;
return `postgresql://${user}:${password}@${host}:${port}/${database}`;
}
}Driver seam: the raw client goes on `this.client`
this.client = new Pool(...) is the short path: configure() builds only the client. getConnector()/beginTransaction() lazily instantiate the class named in @datasource({ driver }) over it - NodePostgresDriver here. There is no pool field - the raw-client slot is client, whatever the client happens to be. Naming the driver class (rather than a driver-name string) is what carries pg into the app's bundle. A bundler only packages a real value reference, never text. The alternative is to wire a driver yourself for a custom or third-party driver: configure() calls this.useDriver({ driver, schema? }). That assigns this.driver and builds this.connector in one step (so the half-wired state cannot exist), bypassing @datasource({ driver }) entirely. See Postgres Drivers & Supabase for postgres-js and Supabase.
How auto-discovery works:
@repositorydecorators register model-datasource bindings in theMetadataRegistrygetSchema()invokesdiscoverSchema()which callsMetadataRegistry.buildSchema({ dataSource })to collect all bound models and their relations- The lazily-built Drizzle connector is initialized with the complete schema (tables + Drizzle relations)
You can disable auto-discovery per datasource via @datasource({ driver: NodePostgresDriver, autoDiscovery: false }).
Manual Schema (Optional)
If you need explicit control, you can still provide schema manually:
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
constructor() {
super({
name: PostgresDataSource.name,
config: { /* ... */ },
schema: {
User: User.schema,
Configuration: Configuration.schema,
// Add relations if using Drizzle's relational queries
},
});
}
}DataSource Hierarchy
AbstractDataSource extends BaseHelper # engine-neutral, src/base - no pool, no Drizzle
└── AbstractPostgresDataSource # connectors/postgres - adds pool, connector
└── BasePostgresDataSource (alias: BaseDataSource)
├── configure() # Assign this.client (abstract) - base wires driver + connector
├── getConnectionString() # Build connection URL (abstract)
├── getSchema() # Auto-discover from @repository bindings
├── discoverSchema() # Internal: reads MetadataRegistry
├── hasDiscoverableModels() # Check if any repos reference this DS
├── getCapabilities() # Returns { transactions: true }
├── beginTransaction(opts?) # Start transaction with isolation level
├── getConnector() # Get Drizzle connector
└── getSettings() # Get connection configRegistering a DataSource
// src/application.ts
export class Application extends BaseApplication {
preConfigure(): ValueOrPromise<void> {
this.dataSource(PostgresDataSource);
}
}DataSources are bound as singletons to ensure connection pool sharing across the application.
Supported Engines
| Engine | Driver/Package | Import | Status |
|---|---|---|---|
| PostgreSQL | node-postgres (pg) | @venizia/ignis or @venizia/ignis/postgres | Supported, transactions + 3 isolation levels |
| Typesense (search) | typesense (optional peer) | @venizia/ignis/typesense (subpath-only) | Supported, no transactions/locks |
| MySQL / SQLite | - | - | Not planned; would be a new connector under src/connectors/ |
DataSource Template
import { BasePostgresDataSource, datasource, ValueOrPromise } from '@venizia/ignis';
import { NodePostgresDriver } from '@venizia/ignis/postgres/node-postgres';
import { Pool } from 'pg';
interface IDSConfigs {
host: string;
port: number;
database: string;
user: string;
password: string;
}
@datasource({ driver: NodePostgresDriver })
export class PostgresDataSource extends BasePostgresDataSource<IDSConfigs> {
constructor() {
super({
name: PostgresDataSource.name,
config: {
host: process.env.APP_ENV_POSTGRES_HOST ?? 'localhost',
port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
database: process.env.APP_ENV_POSTGRES_DATABASE ?? 'mydb',
user: process.env.APP_ENV_POSTGRES_USERNAME ?? 'postgres',
password: process.env.APP_ENV_POSTGRES_PASSWORD ?? '',
},
});
}
override configure(): ValueOrPromise<void> {
this.client = new Pool(this.settings);
}
override getConnectionString(): ValueOrPromise<string> {
const { host, port, user, password, database } = this.settings;
return `postgresql://${user}:${password}@${host}:${port}/${database}`;
}
}Deep Dive: See BaseDataSource Reference for connection pooling and advanced configuration.
See Also
Related Concepts:
- Repositories - Use DataSources for database access
- Models - Entity schemas loaded by DataSource
- Transactions - Multi-operation database transactions
- Search & Typesense - The typesense connector
- Application - Registering DataSources
References:
- BaseDataSource API - Complete API reference
- Environment Variables - Configuration management
External Resources:
- Drizzle ORM Documentation - ORM configuration
- node-postgres Documentation - Connection pooling guide
Best Practices:
- Performance Optimization - Connection pool tuning
- Security Guidelines - Database credential management
Tutorials:
- Complete Installation - Database setup
- Building a CRUD API - DataSource configuration