Demo

Adding new Database source

Prefer the shared default DataSource for most tables. When you need isolation, open a custom TypeORM connection with @Database on the root module, register entities against that plugin + name, and inject with @InjectDatabaseSource.

Goal & when

Most plugin tables should stay on the shared core Postgres connection. Add a custom source only when you need a separate host, schema, reporting database, or replica.

  • Prefer default — register entities with plugin: "default" (no @Database needed)
  • Custom — @Database({ name?, options }) on the root @Module class only
  • Reuse — other plugins inject the owner’s connection; they do not re-declare @Database

1. Prerequisites

You need a working plugin backend module and credentials available via env (PluginEnv / base .env).

  • Plugin scaffolded with root @Module({ name: metadata.name, … })
  • Host, port, username, password, database from env — never hard-code secrets
  • For custom sources set synchronize: false and ship schema via migrations
  • If you will inject another plugin’s DB, list that owner in pluginDependencies

2. Prefer the default DataSource

Unless you need isolation, merge into the shared connection. Inject with DataSourceManager.DEFAULT_PLUGIN (or the string "default").

  • Table names still MUST start with metadata.name
  • Extend BaseEntity for create / update / soft-delete columns
  • No @Database decorator — the core already owns "default"
TSXentities on default
1@Module({ 2 name: metadata.name, 3 providers: [OrderService], 4 controllers: [OrderController], 5 entities: [ 6 { 7 plugin: "default", 8 entities: [OrderEntity], 9 }, 10 ], 11}) 12export class MyPluginModule {}
TSXinject default
1import { DataSource } from "typeorm"; 2import { 3 InjectDatabaseSource, 4 DataSourceManager, 5 Service, 6} from "@quan-erp/shared-backend-core"; 7 8@Service() 9export class OrderService { 10 @InjectDatabaseSource(DataSourceManager.DEFAULT_PLUGIN) 11 source: DataSource; 12}

3. Add a custom @Database

Declare the connection on the root @Module. Register entities with plugin: metadata.name and the same optional name. Inject with @InjectDatabaseSource(metadata.name, name?).

  • @Database({ name?, options }) — plugin owner is filled from the loading plugin
  • name — required when one plugin opens more than one connection
  • options — TypeORM DataSourceOptions; load credentials from env
  • synchronize: false for custom sources in staging / production
  • Entities — matching plugin + name; do not rely on options.entities
TSXroot module — custom source
1import { 2 Database, 3 Module, 4} from "@quan-erp/shared-backend-core"; 5import metadata from "../../module.metadata.json" with { type: "json" }; 6import { AnalyticsEntity } from "./analytics.entity.js"; 7import { AnalyticsService } from "./analytics.service.js"; 8 9@Database({ 10 name: "analytics", 11 options: { 12 type: "postgres", 13 host: process.env.ANALYTICS_DB_HOST, 14 port: Number(process.env.ANALYTICS_DB_PORT ?? 5432), 15 username: process.env.ANALYTICS_DB_USERNAME, 16 password: process.env.ANALYTICS_DB_PASSWORD, 17 database: process.env.ANALYTICS_DB_SCHEMA, 18 synchronize: false, 19 }, 20}) 21@Module({ 22 name: metadata.name, 23 providers: [AnalyticsService], 24 controllers: [], 25 entities: [ 26 { 27 plugin: metadata.name, 28 name: "analytics", 29 entities: [AnalyticsEntity], 30 }, 31 ], 32}) 33export class MyPluginModule {}
TSinject custom
@InjectDatabaseSource(metadata.name, "analytics") analyticsSource: DataSource;

4. Cross-plugin inject

Connections live in a process-wide registry keyed by owner plugin (+ optional name). Consumers inject the owner’s DataSource and may register their own entities against that same plugin + name.

  • Owner — declares @Database and boots first (list in pluginDependencies)
  • Consumer — @InjectDatabaseSource("owner-plugin", "connection-name?")
  • Foreign entities — entities entry uses plugin: "owner-plugin" and name: "connection-name"
  • Do not re-declare the same @Database in every consumer
TSconsumer service
1@Service() 2export class ReportService { 3 // "warehouse" plugin declared @Database({ name: "replica", ... }) 4 @InjectDatabaseSource("warehouse", "replica") 5 warehouseReplica: DataSource; 6}

5. Verify

  • Plugin boots without DI / DataSource errors
  • Injected DataSource is defined and getRepository(Entity) works
  • Entities appear on the intended connection (default vs custom name)
  • Env credentials resolve in Docker / staging the same as local

Common mistakes

  • Putting @Database on a service or nested module — must be root @Module
  • Registering custom-DB entities with plugin: "default" (or wrong name)
  • Hard-coding passwords instead of PluginEnv / base .env
  • synchronize: true on a custom source in production
  • Re-declaring @Database in consumers instead of @InjectDatabaseSource
  • Forgetting pluginDependencies so the owner loads after the consumer