Skip to content

Database Connections

Database connections are the source-of-truth path for durable relational data in a GoForj App.

GoForj keeps database configuration explicit. The Framework-managed internal/database connection registry opens and caches connections on first access.

Open the Default Connection

Database-enabled Apps expose the configured default connection through:

bash
forj db

Expected result: GoForj resolves the default DB_* configuration and opens the matching mysql, psql, or sqlite3 shell. It tries the local client first and falls back to the matching generated Docker Compose service only when the local client is missing and that service exists.

Use --print to verify the masked command without opening a shell:

bash
forj db --print

Application code reaches the same default connection through the generated registry:

go
db, err := conns.Default()

The connection opens on first access and is then cached by name.

Database Package

Database connection behavior lives in:

text
internal/database

The database package owns:

  • database connection configuration
  • first-access connection opening
  • default and named connection access
  • driver-specific support produced during the build
  • local database README guidance

Default Connection

The default connection uses DB_* variables:

text
DB_SUPPORTED_DRIVERS=sqlite,postgres
DB_DRIVER=sqlite
DB_DATABASE=./_data/sqlite/app.db

If DB_DRIVER is unset, GoForj Apps use SQLite. If a SQLite connection does not set DB_DATABASE, the default connection uses _data/sqlite/app.db.

For networked databases, the App can use host, database, username, password, port, pool, and query logging settings.

Named Connections

Named connections use DB_<NAME>_* variables:

text
DB_ANALYTICS_DRIVER=postgres
DB_ANALYTICS_HOST=127.0.0.1
DB_ANALYTICS_DATABASE=analytics
DB_ANALYTICS_USERNAME=app
DB_ANALYTICS_PASSWORD=secret

If a named connection uses the SQLite fallback and no database path is configured, it uses _data/sqlite/<name>.db, such as _data/sqlite/analytics.db.

After changing named connections or supported drivers, use the normal build path:

bash
forj build

During forj dev, an app listed in dev.apps rebuilds automatically. Generation Commands covers focused maintainer workflows.

Accessing Connections

Generated accessors expose default and named connections:

go
db, err := conns.Default()
analytics, err := conns.Analytics()

Connections are opened on first accessor use and cached by name. This database-specific behavior does not imply that every generated manager uses lazy initialization.

Use health and readiness checks to make required database availability visible for the runtime process that needs it.

Shell Options

Use the canonical command when you want the full name:

bash
forj db:shell

Additional apps use the app-name prefix during source-aware development:

bash
forj admin db

When validating a built artifact directly, use its binary:

bash
./bin/app db
./bin/admin db

Named connections use the App-facing connection name:

bash
forj db analytics
forj db --connection analytics

Connection selectors match generated resource names: DB_ANALYTICS_* maps to analytics. With multiple shellable connections, an interactive terminal shows a compact selector. A non-interactive command uses the default connection unless you pass a name; scripts should select one explicitly when they must not depend on that default.

Method Selection

By default, forj db tries the local client first. It falls back to the generated Docker Compose service only when that client is missing and the matching service exists. Other local resolution errors are returned directly; if neither launch method is available, the error identifies the missing client and unavailable Compose fallback.

You can choose the method explicitly:

bash
forj db --method local
forj db --method compose

Use --print to inspect the command GoForj will run. Secrets are masked:

bash
forj db --print
forj db analytics --method local --print

The printed command is useful for checking the selected client, host, port, database, connection arguments, and method without launching it.

Non-Interactive Queries

Use --exec for a single SQL string:

bash
forj db --exec "select count(*) from users"
forj db analytics --exec "select count(*) from events"

Use -- to pass client-native arguments directly after GoForj adds the configured connection arguments:

bash
forj db -- --batch -e "select count(*) from users"
forj db analytics -- -c "select count(*) from events"
forj db --connection analytics -- -c "select now()"

The first example passes MySQL-style arguments. The analytics examples pass Postgres-style arguments when that connection uses Postgres.

Local MySQL launches force TCP when the connection is host-based, preventing the client from silently choosing a local socket.

Driver Support

DB_SUPPORTED_DRIVERS controls which database drivers are generated into the App. DB_DRIVER and DB_<NAME>_DRIVER choose active runtime connections.

Example:

text
DB_SUPPORTED_DRIVERS=sqlite,postgres
DB_DRIVER=sqlite
DB_ANALYTICS_DRIVER=postgres

This compiles SQLite and Postgres support, uses SQLite for the default connection, and uses Postgres for analytics.

Verify a Release

Test repository behavior against the same database engine used in production when SQL dialect, locking, or transaction semantics matter. Before routing traffic to a built artifact, verify the selected connection without exposing its password:

bash
./bin/app db --print
./bin/app db --exec "select 1"

Expected result: the printed command names the intended client, host, port, and database with secrets masked, and the query exits successfully with one row. Repeat with --connection <name> for every connection the release uses.

After the HTTP Runtime starts, readiness is the operational handoff for required database connections:

bash
./bin/app health --probe ready --fail

A non-zero exit means the instance should remain out of traffic while the failed connection, migration, credentials, or network path is repaired.

Source of Truth

Use the database for durable business state.

Do not use cache as the source of truth. Do not use object storage as a replacement for relational state unless the data is actually file/blob data.

Next Steps