---
title: Database overview
description: Organize durable application data, choose a database service, and keep persistence separate from business rules and public APIs.
pagination: false
sidebar:
  icon: database
---

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 here**
>
> Start with [PostgreSQL in local Docker](/docs/modules/database/local-docker). The [Orders
> tutorial](/docs/learn/save-orders) is a complete SQLite alternative. The [Drizzle
> reference](/docs/api/drizzle) lists the exact service, model, and query contracts.

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](/docs/modules/database/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.

1. **Start the database and scaffold its service**

    Follow the [Docker setup](/docs/modules/database/local-docker), 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.

2. **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:

    ```ts title="relkit.config.ts — equivalent inline environment"
    /** 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`.

3. **Migrate and verify**

    Use the [migration steps](/docs/modules/database/local-docker#pre-setup) 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](/docs/learn/save-orders)
    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](/docs/modules/service). It does not replace domain services, and table
access alone never grants permission to a caller. See [Service organization](/docs/modules/service/organization)
for where functions, tools, agents, jobs, and other domain members belong.

## Choose an integration

[Drizzle](/docs/modules/database/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](/docs/learn/save-orders) for a complete
SQLite example, then use [connection setup](/docs/modules/database/define) 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

```text
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](/docs/modules/database/drizzle).

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

```sh
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:**

[Drizzle database service](/docs/modules/database/drizzle).

**Connect and configure lifecycle:**

[Define a Drizzle service](/docs/modules/database/define).

**Define tables, keys, and relationships:**

[Define the schema](/docs/modules/database/schema).

**Reuse and customize generated validation:**

[Generated CRUD and schemas](/docs/modules/database/generated-schemas).

**Read and change records:**

[Query data](/docs/modules/database/queries).

**Add a named query:**

[Custom models](/docs/modules/database/models).

**Change a built-in operation:**

[Override operations](/docs/modules/database/overrides).

**Commit related writes together:**

[Transactions](/docs/modules/database/transactions).

**Evolve persistent data:**

[Migrations](/docs/modules/database/migrations).

**Share the database with authentication:**

[Integrate Better Auth](/docs/modules/database/better-auth).

**Verify behavior safely:**

[Testing](/docs/modules/database/testing).

**Follow the complete application workflow:**

[Your first database](/guides/database/register-users-in-a-transaction).

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/services` API](/docs/api/services)

- [`examples/data-model/src/services/users/service.ts`](https://github.com/rel-kit/relkit/blob/main/examples/data-model/src/services/users/service.ts)
