Skip to content
RELKIT
Esc
↑↓navigate↵open⌘Jpreview
On this page

Database overview

Organize durable application data, choose a database service, and keep persistence separate from business rules and public APIs.

A database keeps application records, such as accounts and orders, after a request or process ends. A database service owns how the application connects to that data and exposes data operations to business domains. It is not the business domain itself.

Start with the data your product needs, then choose the database and integration that fit it. The architecture below applies whether your model uses relational tables or documents; the concrete schema, query, and migration APIs belong to the chosen integration.

Pre-setup

For a new database service, use PostgreSQL in local Docker. That walkthrough covers the Compose file, persistent volume, CLI scaffold, credentials, environment validation, and migration. Complete it before adding queries or auth tables.

Start the database and scaffold its service

Follow the Docker setup, then run bun run relkit add database --orm drizzle --dialect postgresql once in an app with no database service. The CLI installs the integration and creates the service, schema, and migration config.

Connect defineEnv to defineApp

The CLI merges DATABASE_URL into src/platform/env.ts. Keep that contract imported by relkit.config.ts. This minimal inline example shows their relationship:

/** Validate the database connection before activating the Drizzle service. */
import { defineApp, defineEnv, env } from "@relkit/app/config";

export default defineApp({
  env: defineEnv({ DATABASE_URL: env.secret() }),
});

Keep the actual URL in the backend’s untracked .env.local. The Drizzle service opens it inside client({ env }); no database provider profile belongs in defineApp.

Migrate and verify

Use the migration steps to generate, review, and apply your schema, then run bun run check, bun run typecheck, and bun run dev. Existing SQLite/MySQL apps keep their dialect and history; the SQLite tutorial is an explicit alternative, not a conversion step.

Keep the responsibilities separate

Responsibility What belongs here Typical location
Database service Connection configuration, lifecycle, and integration-specific registration src/services/database/service.ts
Data model Stored record shapes, identities, relationships, and constraints src/services/database/schema/ in the current Drizzle integration
Data operations Reusable queries and persistence-specific customization src/services/database/models/ for Drizzle custom methods
Business domain Validation, authorization, and workflows such as registering a member src/services/users/functions/ or another owning domain
Operational tooling Schema migrations, backfills, backups, and recovery Application-root tooling, outside business handlers
HTTP interface Which business operations callers can invoke src/routes/

A Users function decides whether someone may join an organization. The database stores the user and membership and enforces the configured constraints. A route exposes that workflow only when the product needs an HTTP endpoint. Keeping these responsibilities separate lets you change a query without moving routes, and evolve storage without duplicating business rules across transports.

The database is a specialized service. It does not replace domain services, and table access alone never grants permission to a caller. See Service organization for where functions, tools, agents, jobs, and other domain members belong.

Choose an integration

Drizzle is the database service integration available today. It supports relational table definitions and adds typed CRUD operations, generated validation schemas, custom models, and transactions.

MongoDB and Prisma integrations are planned, not available APIs in this release. Their guides will describe their own APIs as they are introduced; Drizzle’s method names are not a universal contract for future integrations. Database concepts stay separate from the currently implemented Drizzle workflow.

For the Drizzle path, follow Save and retrieve orders for a complete SQLite example, then use connection setup for other databases. Other database clients should remain behind your domain’s own operations until a supported integration is available; they are not automatically registered by naming a file service.ts.

Follow data through the application

Route, job, tool, or other entry point
  → owning domain's business function
  → database service's data operations
  → database

Schema changes → migration tooling → database

The current Drizzle integration makes its typed client available through context.database. Business functions do not open a new connection for each call or declare dependencies.database. This injection and its exact types are documented in the Drizzle service guide.

From an existing app’s root, check service discovery and generated context types after changing registration:

bun run check
bun run typecheck

These commands check application structure and types; they do not provision a database or migrate it.

Find the guide for your task

The following implementation guides use the current Drizzle integration:

Understand what the integration generates:

Connect and configure lifecycle:

Define tables, keys, and relationships:

Reuse and customize generated validation:

Read and change records:

Add a named query:

Change a built-in operation:

Commit related writes together:

Evolve persistent data:

Share the database with authentication:

Verify behavior safely:

Follow the complete application workflow:

Reference and source