Use Drizzle the same way you would in any framework app: connect the node-postgres driver to DATABASE_URL, define your schema with drizzle-orm/pg-core, and import the database client from the routes or jobs that need it.
Use this path when you want schema-first TypeScript queries and migrations while still connecting through the standard Postgres URL that Agentuity writes for the project.
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pgimport { boolean, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
active: boolean('active').notNull().default(true),
createdAt: timestamp('created_at').defaultNow(),
});import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL is required');
}
export const pool = new Pool({ connectionString: databaseUrl });
export const db = drizzle(pool, { schema });Keep both pool and db at module scope. Framework routes, queue consumers, schedules, and scripts can import db without creating a new connection pool for every request.
Get DATABASE_URL
Link a database resource to the project before running migrations or local routes:
agentuity project add database app_dataThat command writes DATABASE_URL to .env. To recover the connection string later, use:
agentuity cloud db get app_data --show-credentialsThe default terminal output masks credentials. Use --show-credentials only in a trusted shell.
Framework Route
Initialize the database client at module scope, then import it into framework routes, server functions, queue consumers, or scripts.
import { eq } from 'drizzle-orm';
import { db } from '@/src/db/client';
import { users } from '@/src/db/schema';
export async function GET(): Promise<Response> {
const activeUsers = await db
.select({ id: users.id, email: users.email })
.from(users)
.where(eq(users.active, true))
.limit(20);
return Response.json({ users: activeUsers });
}Use Drizzle placeholders and query builders for dynamic values. If a route needs raw SQL, use Drizzle's sql helper instead of concatenating request values into SQL strings.
Migrations
Use drizzle-kit for schema generation and migrations. The config reads the same DATABASE_URL that Agentuity writes when you link a database to the project.
import { defineConfig } from 'drizzle-kit';
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
throw new Error('DATABASE_URL is required for Drizzle migrations');
}
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: databaseUrl,
},
});bunx drizzle-kit generate
bunx drizzle-kit migrateSee the Drizzle migrations documentation for migration modes and configuration options.
For a quick local schema push while developing, use:
bunx drizzle-kit pushUse generate and migrate when you want checked-in migration files and repeatable deploy-time migration steps.
Transactions
Use Drizzle's transaction API for atomic operations.
import { eq, sql } from 'drizzle-orm';
import { db } from './client';
import * as schema from './schema';
await db.transaction(async (tx) => {
await tx
.update(schema.accounts)
.set({ balance: sql`balance - ${100}` })
.where(eq(schema.accounts.name, 'Alice'));
await tx
.update(schema.accounts)
.set({ balance: sql`balance + ${100}` })
.where(eq(schema.accounts.name, 'Bob'));
});Keep the transaction boundary near the route, job, or service function that owns the write. Avoid splitting one business operation across separate transactions unless the partial states are valid.
Existing Drizzle Apps
If the app already has a Drizzle setup, keep it and swap the connection URL to DATABASE_URL. The important v3 change is the connection source, not the ORM shape.
| Existing setup | Keep |
|---|---|
drizzle-orm/node-postgres | the same driver and schema files |
| checked-in migrations | the existing drizzle output directory |
| app-owned auth/session tables | the same table definitions and relation queries |
| framework route imports | the same db import pattern |
Use Postgres Clients when SQL is clearer than an ORM. Use Database Overview for trusted scripts that need DBClient against the Agentuity database service API.
Auth and User Tables
In v3 apps, keep authentication in your framework or identity provider, then use Drizzle for the user, account, or session tables your app owns. See Authentication for choosing between framework-owned sessions and Agentuity OIDC.
Next Steps
- Database Overview: managed Postgres setup and provisioning
- Postgres Clients: query with
pg - Database API Reference: inspect the service API used by trusted admin scripts