Define a Drizzle service
Install compatible dependencies, configure a Drizzle client and environment, generate context types, and manage connection lifecycle.
Register your database connection and tables with defineDrizzleService in src/services/database/service.ts. Use defineDrizzleService
from @relkit/drizzle in src/services/database/service.ts. Do not wrap it in an
ordinary defineService, add a database member to another service, or add a database provider profile
to relkit.config.ts. The specialized service is the registration.
Pre-setup
For a new database, complete PostgreSQL in local Docker
before configuring the service. It supplies the running database, DATABASE_URL contract,
and migration steps. The detailed manual example below uses SQLite; keep your existing
dialect and migrations when following it, and use the corresponding native driver.
Install compatible packages
The minimal app does not install the optional database integration. Choose one setup path from the application root, then continue with the connection and schema below.
Scaffold the database baseline
bun run relkit add database --orm drizzle --dialect postgresqlThis creates src/services/database/service.ts, an items placeholder in
src/services/database/schema/index.ts, and drizzle.config.ts. It merges the environment
contract and examples, installs the selected packages and declaration repair, and checks the app.
Replace the placeholder schema with your tables. The command does not apply migrations.
Use this once when the application has no database service. For an existing service, edit its
configuration and preserve its schema and migration history. Use --dialect sqlite for the
manual file-backed example below, or --dialect mysql with a running MySQL database.
With --no-install, run bun install and bun run check before continuing. See the
database command reference and
complete SQLite guide.
Install the compatible owner-specific cohort before creating the files below. RELKIT packages have independent versions; keep existing manifest settings when merging the repair.
From this guide’s application/package root, install only its selected requirements:
bun add @relkit/drizzle@0.8.1 drizzle-orm@1.0.0-rc.5-169397b
bun add --dev create-relkit@0.8.4 drizzle-kit@1.0.0-rc.5-ab785fcThe selected Drizzle version also needs the release’s declaration repair. The development-only create-relkit package supplies its exact asset; a dependency cannot register Bun patches for its consumer.
mkdir -p patches
cp node_modules/create-relkit/dist/patches/drizzle-orm.patch patches/drizzle-orm@1.0.0-rc.5-169397b.patchMerge this entry into the application’s existing patchedDependencies, preserving other patches. Keep the asset in source control:
{
"patchedDependencies": {
"drizzle-orm@1.0.0-rc.5-169397b": "patches/drizzle-orm@1.0.0-rc.5-169397b.patch"
}
}Then apply the registered repair before check/typecheck:
bun installKeep these owner-specific versions and the lockfile together. Runtime SDKs belong in dependencies; build/type tooling belongs in devDependencies. Installing an adapter does not supply its optional SDK peers or enable the feature’s configuration.
Bun includes the SQLite driver used here. The scaffold’s PostgreSQL/MySQL clients use bun:sql;
other native Drizzle clients need their own selected driver and a running database. The
MySQL model entrypoint also requires its declaration peer. Do not mix
older Drizzle APIs with this release candidate without checking types.
Configure environment and client creation
The data-model example uses this environment contract:
import { defineEnv, env } from "@relkit/app/config";
export default defineEnv({ DATABASE_PATH: env.string().default("./data-model.sqlite") });Merge DATABASE_PATH into your existing defineEnv() and keep that descriptor in defineApp({ env }).
The example defaults to ./data-model.sqlite; ensure its parent directory exists and is writable.
Use the same path for the app and migration tooling. A test may explicitly
use :memory:, which is isolated to a connection and disappears when it closes.
The complete service includes optional models and overrides:
/** Owns lazy SQLite registration and disposal, supplying users-model extensions and an inactive-user lookup override. */
import { Database } from "bun:sqlite";
import { defineDrizzleService } from "@relkit/drizzle";
import { drizzle } from "drizzle-orm/bun-sqlite";
import * as schema from "./schema/index.js";
import usersModel from "./models/users.model.js";
export default defineDrizzleService({
schema,
client: ({ env }) => drizzle({ client: new Database(env.DATABASE_PATH) }),
models: { users: usersModel },
overrides: {
users: {
findOne: async ({ args, base }) => {
const user = await base(args);
return user?.active === false ? null : user;
},
},
},
dispose: (database) => database.$client.close(),
});schema is the flat map of table exports. client({ env }) returns the actual Drizzle database, or a
promise for it. Open the connection inside this factory, not at module top level. Descriptor discovery
inspects the schema without activating the factory; the generated server activates it during startup.
There are no table-creation statements to write here. The client factory only opens the connection. Define the model in Drizzle tables, then use Drizzle Kit to generate and apply its migrations before starting the app.
models and overrides are optional; omit both for base CRUD. dispose(database) closes the underlying
driver or pool. RELKIT calls it during normal runtime shutdown. A process crash still requires normal
database recovery; shutdown hooks are not backup or durability guarantees.
Choose and customize the driver
| Dialect | What changes | Important integration limit |
|---|---|---|
| SQLite | SQLite tables, Bun SQLite client, file path or memory database | The portable transaction helper shares a connection; review its concurrency limits. |
| PostgreSQL | PostgreSQL table definitions and a compatible async Drizzle client/pool | Portable transactions delegate to the client’s transaction implementation. |
| MySQL | MySQL tables and a compatible async Drizzle client/pool | Returned mutation rows need a recoverable primary/unique key; see queries. |
Driver construction remains native Drizzle configuration. Configure TLS, connection limits, query timeouts, logging, and pool disposal there as appropriate for that driver. RELKIT does not add these as service options or translate tables between dialects. The executable guides here verify Bun SQLite; test other drivers against their actual database before deploying.
Use secret environment fields for credentials or a credential-bearing connection URL. Do not log them or
hard-code production values. RELKIT_ENV=test does not replace this custom client factory with an
in-memory database: set a safe test database value explicitly.
Generate the typed application context
From the app root:
bun run check
bun run typecheckThe compiler emits .relkit/generated/context-registry.d.ts, which supplies context.database and
typed application environment fields. Keep the generated declaration included by your TypeScript
configuration, as generated projects do. Do not hand-edit it or add a competing global declaration.
If the database or a model method is missing from editor types, check table exports and model registration,
rerun the compiler, then confirm the declaration is included. The service has an inferred identity from
its domain; an explicit id is optional and must follow the same domain identity rules.
Understand activation ownership
Activation is cached by descriptor object within a process. Repeated activation shares the original
client and environment; it is not a per-request or per-tenant connection factory. close() is idempotent,
but currently does not evict that cached activation, so activating the same closed descriptor does not
reopen it. Use fresh service objects or isolated processes for independent test lifetimes.
The service is not a managed database provisioning request. Arrange durable storage or a database server, backups, credentials, network access, and migrations separately. For Better Auth, keep the auth tables in this same Drizzle schema and follow Integrate Better Auth.