6.3 KiB
6.3 KiB
Neon and Drizzle Integration
Integration patterns, configurations, and optimizations for using Drizzle ORM with Neon Postgres.
For official documentation:
curl -H "Accept: text/markdown" https://neon.com/docs/guides/drizzle
Choosing the Right Driver
Drizzle ORM works with multiple Postgres drivers. See connection-methods.md for the full decision tree.
| Platform | TCP Support | Pooling | Recommended Driver |
|---|---|---|---|
| Vercel (Fluid) | Yes | @vercel/functions |
pg (node-postgres) |
| Cloudflare (Hyperdrive) | Yes | Hyperdrive | pg (node-postgres) |
| Cloudflare Workers | No | No | @neondatabase/serverless |
| Netlify Functions | No | No | @neondatabase/serverless |
| Deno Deploy | No | No | @neondatabase/serverless |
| Railway / Render | Yes | Built-in | pg (node-postgres) |
Connection Setup
1. TCP with node-postgres (Long-Running Servers)
Best for Railway, Render, traditional VPS.
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg dotenv
// src/db.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle({ client: pool });
2. Vercel Fluid Compute with Connection Pooling
npm install drizzle-orm pg @vercel/functions
npm install -D drizzle-kit @types/pg
// src/db.ts
import { attachDatabasePool } from "@vercel/functions";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
attachDatabasePool(pool);
export const db = drizzle({ client: pool, schema });
3. HTTP Adapter (Edge Without TCP)
For Cloudflare Workers, Netlify Edge, Deno Deploy. Does NOT support interactive transactions.
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit dotenv
// src/db.ts
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);
4. WebSocket Adapter (Edge with Transactions)
npm install drizzle-orm @neondatabase/serverless ws
npm install -D drizzle-kit dotenv @types/ws
// src/db.ts
import { drizzle } from "drizzle-orm/neon-serverless";
import { Pool, neonConfig } from "@neondatabase/serverless";
import ws from "ws";
neonConfig.webSocketConstructor = ws; // Required for Node.js < v22
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle(pool);
Drizzle Config
// drizzle.config.ts
import { config } from "dotenv";
import { defineConfig } from "drizzle-kit";
config({ path: ".env.local" });
export default defineConfig({
schema: "./src/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Migrations
# Generate migrations
npx drizzle-kit generate
# Apply migrations
npx drizzle-kit migrate
Schema Definition
// src/schema.ts
import { pgTable, serial, text, integer, timestamp } from "drizzle-orm/pg-core";
export const usersTable = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
role: text("role").default("user").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type User = typeof usersTable.$inferSelect;
export type NewUser = typeof usersTable.$inferInsert;
export const postsTable = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content").notNull(),
userId: integer("user_id")
.notNull()
.references(() => usersTable.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export type Post = typeof postsTable.$inferSelect;
export type NewPost = typeof postsTable.$inferInsert;
Query Patterns
Batch Inserts
export async function batchInsertUsers(users: NewUser[]) {
return db.insert(usersTable).values(users).returning();
}
Prepared Statements
import { sql } from "drizzle-orm";
export const getUsersByRolePrepared = db
.select()
.from(usersTable)
.where(sql`${usersTable.role} = $1`)
.prepare("get_users_by_role");
// Usage: getUsersByRolePrepared.execute(['admin'])
Transactions
export async function createUserWithPosts(user: NewUser, posts: NewPost[]) {
return await db.transaction(async (tx) => {
const [newUser] = await tx.insert(usersTable).values(user).returning();
if (posts.length > 0) {
await tx.insert(postsTable).values(
posts.map((post) => ({
...post,
userId: newUser.id,
})),
);
}
return newUser;
});
}
Working with Neon Branches
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
const getBranchUrl = () => {
const env = process.env.NODE_ENV;
if (env === "development") return process.env.DEV_DATABASE_URL;
if (env === "test") return process.env.TEST_DATABASE_URL;
return process.env.DATABASE_URL;
};
const sql = neon(getBranchUrl()!);
export const db = drizzle({ client: sql });
Error Handling
export async function safeNeonOperation<T>(
operation: () => Promise<T>,
): Promise<T> {
try {
return await operation();
} catch (error: any) {
if (error.message?.includes("connection pool timeout")) {
console.error("Neon connection pool timeout");
}
throw error;
}
}
Best Practices
- Connection Management - See
connection-methods.mdfor platform-specific guidance - Neon Features - Utilize branching for development/testing (see
features.md) - Query Optimization - Batch operations, use prepared statements
- Schema Design - Leverage Postgres-specific features, use appropriate indexes