Because WattleDB is plain PostgreSQL, there's no special driver or adapter — Prisma, Drizzle, TypeORM, whatever you like, all connect normally. The only thing to get right is which connection string does which job. Get that right and everything from migrations to serverless just works.
The rule for every ORM
- App / query runtime → pooled
DATABASE_URL. - Migrations → direct
DIRECT_URL. - Always keep
sslmode=require— both endpoints are TLS.
Two URLs, two jobs
Your app makes lots of short queries under load — that's the pooled DATABASE_URL's job (see connection pooling). Migrations create objects and hold longer transactions that a transaction pooler breaks — that's the direct DIRECT_URL's job. Every ORM setup below is just wiring those two strings to those two roles.
Prisma
Prisma has first-class support for exactly this split. Point url at the pooled endpoint and directUrl at the direct one — Prisma uses url at runtime and directUrl for prisma migrate and introspection:
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // pooled — app queries
directUrl = env("DIRECT_URL") // direct — migrations
}
# .env
DATABASE_URL="postgresql://app:***@<id>.db.wattledb.com.au:PORT/app?sslmode=require&pgbouncer=true"
DIRECT_URL="postgresql://app:***@<id>.db.wattledb.com.au:PORT/app?sslmode=require"
The pgbouncer=true flag tells Prisma it's talking to a transaction pooler, so it manages prepared statements accordingly.
Drizzle
Drizzle uses a Postgres driver directly. For the pooled connection with postgres.js, disable prepared statements so the transaction pooler is happy; use the direct URL in your Drizzle Kit migration config:
// db.ts — runtime, pooled
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
const client = postgres(process.env.DATABASE_URL!, { prepare: false });
export const db = drizzle(client);
// drizzle.config.ts — migrations, direct
export default {
schema: "./src/schema.ts",
dbCredentials: { url: process.env.DIRECT_URL! },
} satisfies Config;
TLS and the CA certificate
Both endpoints require TLS — keep sslmode=require in your connection strings. For verified TLS (recommended in production), download the CA certificate from the console and point your driver at it with sslmode=verify-full and the cert path, so the client checks it's really talking to your database and not a man in the middle.
Common pitfalls
- Migrations failing or hanging? They're probably running through the pooled URL. Point your migration command at
DIRECT_URL. - "prepared statement already exists"? Disable prepared statements on the pooled connection (
prepare: false/pgbouncer=true). - Connection refused / SSL errors? Make sure
sslmode=requireis present — WattleDB doesn't accept unencrypted connections. - Too many connections under load? Confirm your app is on the pooled
DATABASE_URL, not the direct one.