Appwrite offers dedicated, native PostgreSQL databases that you connect to directly over TLS. There is no Appwrite-specific driver or abstraction layer, so you can use Drizzle with either the node-postgres or postgres.js driver, the same way you would with any PostgreSQL server.
To get your connection string, open your database in the Appwrite Console and click Credentials. The Drizzle tab contains a ready-made snippet, and the DSN and .env tabs contain the raw connection string. You can also fetch it from the Appwrite API with postgresql.get(). The connection string already includes sslmode=require, so no extra TLS configuration is needed.
bun add drizzle-orm@rc postgresbun add -D drizzle-kit@rc
Step 2 - Initialize the driver and make a query
node-postgres (pg)
postgres.js
// Make sure to install the 'pg' packageimport { drizzle } from 'drizzle-orm/node-postgres';const db = drizzle(process.env.DATABASE_URL);const result = await db.execute('select 1');
// Make sure to install the 'postgres' packageimport { drizzle } from 'drizzle-orm/postgres-js';const db = drizzle(process.env.DATABASE_URL);const result = await db.execute('select 1');
If you need to provide your existing drivers:
node-postgres (pg)
postgres.js
// Make sure to install the 'pg' packageimport { drizzle } from "drizzle-orm/node-postgres";import { Pool } from "pg";const pool = new Pool({ connectionString: process.env.DATABASE_URL,});const db = drizzle({ client: pool });const result = await db.execute('select 1');
// Make sure to install the 'postgres' packageimport { drizzle } from 'drizzle-orm/postgres-js';import postgres from 'postgres';const queryClient = postgres(process.env.DATABASE_URL);const db = drizzle({ client: queryClient });const result = await db.execute('select 1');
Connection pooling for serverless
Appwrite runs a connection pooler on port 6432 of the same hostname. In serverless and edge environments, route your application traffic through the pooler so short-lived instances don’t exhaust the engine’s connection limit, and keep Drizzle Kit pointed at the direct engine port 5432, because migrations issue DDL that needs a session-level connection.
# Runtime: pooled, transaction modeDATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"# Migrations & introspection: direct connection to the engineDIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
The pooler defaults to transaction mode, which does not keep a backend connection across statements, so server-side prepared statements are unavailable. With postgres.js, build the client yourself with prepare: false when connecting through the pooler:
import { drizzle } from 'drizzle-orm/postgres-js';import postgres from 'postgres';const queryClient = postgres(process.env.DATABASE_URL, { prepare: false });const db = drizzle({ client: queryClient });
If you rely on prepared statements, advisory locks, LISTEN/NOTIFY, temporary tables or SET LOCAL, switch the pooler to session mode or use the direct port.