Skip to main content

Databases

Bunway supports multiple named databases. PostgreSQL is the default. PostgreSQL, MySQL, and SQLite connections are ordinary Drizzle instances. Bunway provides configuration and generators, not an ORM.

Choose an adapter

AdapterBest fitMigrationsJobsGenerated SQL Auth/Audit
PostgreSQLdefault production application; full field setDrizzle Kityesyes
MySQLexisting MySQL systems and conventional SQL appsDrizzle Kituse named PostgreSQLyes
SQLitelocal, embedded, and small single-host dataDrizzle Kituse named PostgreSQLyes

PostgreSQL supports arrays and engine-specific types. MySQL and SQLite emit native Drizzle builders and reject PostgreSQL-only fields.

The simple case

New applications use PostgreSQL and keep the familiar API:

import { db } from './db'

const products = await db.select().from(productsTable)

db is the primary connection. Its schema and migrations remain in src/db/schema/ and src/db/migrations/.

Choose another primary adapter non-interactively:

bun create bunway tinyapp --database=sqlite
bun create bunway shop --database=mysql

Named databases

bunway db:add analytics --adapter=postgres
bunway db:add legacy --adapter=mysql
bunway db:add local --adapter=sqlite
bunway db:list

Configuration is small and explicit:

import { defineDatabases } from '@bunway/core'

export default defineDatabases({
primary: { adapter: 'postgres', url: Bun.env.DATABASE_URL },
analytics: { adapter: 'postgres', url: Bun.env.ANALYTICS_DATABASE_URL },
legacy: { adapter: 'mysql', url: Bun.env.LEGACY_DATABASE_URL },
local: { adapter: 'sqlite', url: './storage/local.sqlite' },
})

src/db/index.ts exports statically typed clients:

import { db, analytics, legacy, local } from './db'

const order = await db.select().from(orders)
const metrics = await analytics.select().from(metricsTable)
const oldCustomers = await legacy.select().from(legacyCustomers)
const preferences = await local.select().from(localPreferences)

This supports a realistic split: primary PostgreSQL application data, PostgreSQL analytics, an existing MySQL system, and local SQLite auxiliary data. Credentials are never shown by db:list.

Generators and schemas

Generators target primary unless selected:

bunway g resource Product name:string price:decimal
bunway g model Event name:string payload:json --database=analytics
bunway g resource LegacyCustomer name:string --database=legacy

Each SQL database owns its Drizzle schema. Added databases use src/db/<name>/schema/ and src/db/<name>/migrations/; the primary database keeps the shallow existing layout. Builders, IDs, and timestamps are emitted for the selected dialect. All three SQL adapters use Bun.randomUUIDv7() for UUID primary keys by default. PostgreSQL-only fields such as arrays, jsonb, network types, and timezone-specific types fail clearly on other adapters. Advanced engine-specific types remain handwritten ordinary Drizzle code.

Choose uuid, integer, or bigint with --id-type. UUIDs use the standard representation by default; choose the compact representation with --id-encoding=base64url, or set BUNWAY_ID_ENCODING=base64url for generator invocations. Standard UUIDs use PostgreSQL's native uuid; base64url IDs use 22-character text/varchar columns on every SQL adapter. Relationship generators inherit both the primary-key type and UUID encoding.

Relationships are resolved only inside the selected database. Bunway never creates a cross-database foreign key. Store an external identifier explicitly when records live in independent databases.

Migrations

bunway db:migrate
bunway db:migrate --database=analytics
bunway db:migrate --all

Every database has a small Drizzle Kit config (drizzle.config.ts for primary and drizzle.<name>.config.ts for additional databases). Bunway generates migration SQL with Drizzle Kit, applies it with Drizzle ORM and the adapter's native client, reports the name, and stops on failure. --all is sequential and is not presented as a cross-database transaction.