Drizzle ORM

Type-safe database access with Drizzle ORM and Agentuity-managed Postgres

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/pg
typescriptsrc/db/schema.ts
import { 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(),
});
typescriptsrc/db/client.ts
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_data

That command writes DATABASE_URL to .env. To recover the connection string later, use:

agentuity cloud db get app_data --show-credentials

The 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.

typescriptapp/api/users/route.ts
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.

typescriptdrizzle.config.ts
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 migrate

See the Drizzle migrations documentation for migration modes and configuration options.

For a quick local schema push while developing, use:

bunx drizzle-kit push

Use 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 setupKeep
drizzle-orm/node-postgresthe same driver and schema files
checked-in migrationsthe existing drizzle output directory
app-owned auth/session tablesthe same table definitions and relation queries
framework route importsthe 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