Data sources manage database connections in HeronJS. They define how your application connects to systems such as MySQL, PostgreSQL, or Oracle, and make those connections available throughout your modules.
Create a data source configuration
Start by defining a configuration object for each database you want to connect to. This configuration describes the database client, connection credentials, driver, pooling, and optional cluster settings.
PostgreSQL
export const PostgresDatabaseConfiguration = {
client: DatabaseClient.KNEX,
config: <CommonDatabaseConfig>{
host: process.env.POSTGRES_HOST || 'localhost',
port: process.env.POSTGRES_PORT || 5432,
user: process.env.POSTGRES_USER || 'root',
password: process.env.POSTGRES_PASSWORD || 'root@123',
database: process.env.POSTGRES_DATABASE || 'test',
pooling: { min: 1, max: 10 },
driver: DatabaseDriver.POSTGRES,
cluster: {
secondaries: [
{
host: 'localhost',
port: 5433,
},
],
},
},
};MySQL and MariaDB
export const MySQLDatabaseConfiguration = {
client: DatabaseClient.KNEX,
config: <CommonDatabaseConfig>{
host: process.env.MYSQL_HOST || 'localhost',
port: process.env.MYSQL_PORT || 3306,
user: process.env.MYSQL_USER || 'dev',
password: process.env.MYSQL_PASSWORD || 'dev@123',
database: process.env.MYSQL_DATABASE || 'dev',
pooling: { min: 1, max: 10 },
driver: DatabaseDriver.MYSQL,
cluster: {
secondaries: [
{
host: 'localhost',
port: 3307,
},
],
},
},
};In both examples:
clientselects the underlying database client implementation.configcontains the connection settings for the driver.poolingcontrols how many connections can be reused.cluster.secondarieslets you define additional replica connections.
Create a data source class
After defining the configuration, create a class that extends AbstractDataSourceClient. This class becomes the injectable data source used by the rest of the application.
@UseConfig(PostgresDatabaseConfiguration)
export class PostgresDatabase extends AbstractDataSourceClient<KnexClient> {
constructor() {
super();
}
context(): DatabaseContext<any> {
return this;
}
}Here:
@UseConfig(...)attaches the configuration object to the data source.AbstractDataSourceClientprovides the base behavior for supported database clients.context()exposes the database context that other parts of the application can consume.
Create a custom database client
If you need to work with a database that does not use the built-in data source client, you can implement your own client by extending AbstractDatabaseClient.
@Default()
export class CassandraDatabase extends AbstractDatabaseClient<Any> {
init(): DatabaseConnector<Any> {
return {
connect: () => {
// database connection behavior
},
get: () => {
// database instance
},
};
}
}Use this pattern when you need full control over how the connection is created and exposed.
Multi-tenant data source
For tenant-aware applications, use AbstractTenancyDataSourceClient instead of the standard data source base class.
@UseConfig(PostgresDatabaseConfiguration)
export class PostgresDatabase extends AbstractTenancyDataSourceClient<KnexClient> {
constructor() {
super();
}
}This allows the data source to resolve connections based on tenant context.
Multi-primary data source
If your deployment uses writable secondary nodes, mark them explicitly in the cluster configuration.
export const PostgresDatabaseConfiguration = {
client: DatabaseClient.KNEX,
config: <CommonDatabaseConfig>{
host: process.env.POSTGRES_HOST || 'localhost',
port: process.env.POSTGRES_PORT || 5432,
user: process.env.POSTGRES_USER || 'root',
password: process.env.POSTGRES_PASSWORD || 'root@123',
database: process.env.POSTGRES_DATABASE || 'test',
pooling: { min: 1, max: 10 },
driver: DatabaseDriver.POSTGRES,
cluster: {
secondaries: [
{
host: 'localhost',
port: 5433,
writable: true,
},
],
},
},
};Set writable: true only when that node should accept write operations.
Set a default data source
Use @Default() when you want a data source to be selected automatically during lookup.
@UseConfig(PostgresDatabaseConfiguration)
@Default()
export class PostgresDatabase extends AbstractDataSourceClient<KnexClient> {
constructor() {
super();
}
context(): DatabaseContext<any> {
return this;
}
}This is useful when most of your application should use one primary database without specifying its name each time.
Register data sources in the root module
To make data sources available to the application, register them in the root module with @DataSources(...).
@Module({
imports: [TodosModule],
})
@DataSources([MySQLDatabase, PostgresDatabase])
export class AppModule {}Once registered, the data sources can be injected into providers and services across your modules.
Look up a data source
Use @DatabaseLookup() to inject a registered data source into a provider.
@DataSource()resolves the default data source.@DataSource('name')resolves a specific data source by name or token.
import { Provider, Scope } from '@heronjs/common';
@Provider({
scope: Scope.SINGLETON,
})
export class TodoProvider {
constructor(@DataSource() private readonly db: ModuleDatabase<KnexClient>) {}
async findAll() {
return ['todo1'];
}
}To inject a specific database instead of the default one:
import { Provider, Scope } from '@heronjs/common';
@Provider({
scope: Scope.SINGLETON,
})
export class ReportingProvider {
constructor(
@DataSource('other-database') private readonly db: ModuleDatabase<KnexClient>,
) {}
}Supported decorators
@UseConfigattaches a configuration object to a data source.@Defaultmarks a data source as the default lookup target.@DatabaseLookupinjects a registered data source into a class.