How to configure databases
tango.config.ts is the normal place to describe database configuration in a Tango application.
That file keeps development, test, and production settings together, and it gives the runtime code and the tango CLI the same source of truth.
Start with tango.config.ts
Generated Tango applications define their database settings with @danceroutine/tango-config:
import { defineConfig } from '@danceroutine/tango-config';
export default defineConfig({
current: (process.env.NODE_ENV || 'development') as 'development' | 'test' | 'production',
environments: {
development: {
name: 'development',
db: {
adapter: 'sqlite',
filename: './.data/app.sqlite',
maxConnections: 1,
},
migrations: { dir: './migrations', online: false },
},
test: {
name: 'test',
db: {
adapter: 'sqlite',
filename: ':memory:',
maxConnections: 1,
},
migrations: { dir: './migrations', online: false },
},
production: {
name: 'production',
db: {
adapter: 'postgres',
url: process.env.TANGO_DATABASE_URL,
maxConnections: 20,
},
migrations: { dir: './migrations', online: true },
},
},
});At runtime, application code resolves the active environment with loadConfig() and then reads loaded.current.db.
import { loadConfig } from '@danceroutine/tango-config';
import tangoConfig from '../tango.config';
const loadedConfig = loadConfig(() => tangoConfig);
const db = loadedConfig.current.db;SQLite configuration
SQLite is the simplest choice for local development, examples, and fast integration loops. In Tango, that usually means:
adapter: 'sqlite'- a project-local file such as
./.data/app.sqlitefor development :memory:for test runs that should stay isolated and fastmaxConnections: 1, because SQLite usually runs as a single-process local database
The generated application scaffolds follow that pattern directly in tango.config.ts.
PostgreSQL configuration
PostgreSQL is the stronger choice when you want production parity, realistic connection behavior, and migration validation against the backend you expect to deploy.
You can describe PostgreSQL in two common ways:
- with a single
url - with discrete fields such as
host,port,database,user, andpassword
The loader supports both shapes. Application code can then pass the resolved values into PostgresAdapter.connect().
db: {
adapter: 'postgres',
url: process.env.TANGO_DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/app',
maxConnections: 10,
}A practical environment strategy
Most teams should keep the strategy simple:
- use SQLite for local iteration when startup speed matters
- use PostgreSQL in CI for backend-parity integration checks
- use the same PostgreSQL family in production that you test in CI
That setup gives developers a fast inner loop while still forcing the project to prove its behavior against the production-style backend before merge.
Environment overrides
loadConfig() loads .env automatically and then merges a fixed set of Tango-specific overrides into the selected environment.
The most common overrides are:
TANGO_DATABASE_URLorDATABASE_URLfor PostgreSQL URLsTANGO_SQLITE_FILENAMEfor SQLite filesTANGO_DB_HOST,TANGO_DB_PORT,TANGO_DB_NAME,TANGO_DB_USER, andTANGO_DB_PASSWORDfor discrete PostgreSQL settingsTANGO_MIGRATIONS_DIRandTANGO_MIGRATIONS_ONLINEfor migration behavior
That lets you keep the broad shape of the environment in tango.config.ts while still injecting deployment-specific values from the process environment.
How the CLI uses config
The migrations CLI auto-loads ./tango.config.ts when the file exists. That allows commands such as tango migrate and tango make:migrations to infer:
- the dialect from
current.db.adapter - the database target from
current.db - the migrations directory from
current.migrations.dir
Use --config when the file lives somewhere else. Use --env when you want the command to resolve development, test, or production from the same file. Use explicit flags such as --db, --dialect, or --dir when you need a one-off override.
Multi-database applications
Tango does not impose a database routing policy, so multi-database work should stay explicit in application code.
The safest pattern is to centralize:
- connection names
- adapter selection
- manager-to-connection mapping
- migration execution per database
That centralization keeps database policy visible and avoids burying connection assumptions deep inside repositories.
Operational advice
Whichever backend you choose, make these behaviors explicit:
- application startup should fail fast on invalid or missing database configuration
- migration application should happen through a deliberate command or deployment step
- integration tests should cover the backends you claim to support