重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
neon-serverless-postgres by bobmatnyc/claude-mpm-skills
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill neon-serverless-postgres# 直接连接(用于迁移、管理任务)
DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"
# 池化连接(用于应用查询)
DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
# PgBouncer 池化连接(推荐用于无服务器)
DATABASE_URL="postgresql://user:password@ep-xxx-pooler.region.aws.neon.tech/dbname?sslmode=require"
# 用于迁移的直接连接
DIRECT_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"
// drizzle.config.ts
import type { Config } from "drizzle-kit";
export default {
schema: "./src/db/schema.ts",
out: "./drizzle",
driver: "pg",
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;
// src/db/index.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);
// src/db/schema.ts
import { pgTable, serial, text, timestamp, varchar } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
userId: serial("user_id").references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
});
import { db } from "./db";
import { users, posts } from "./db/schema";
import { eq } from "drizzle-orm";
// 插入
const newUser = await db.insert(users).values({
name: "John Doe",
email: "john@example.com",
}).returning();
// 查询
const allUsers = await db.select().from(users);
// 连接
const userPosts = await db
.select()
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
// 更新
await db.update(users)
.set({ name: "Jane Doe" })
.where(eq(users.id, 1));
# 生成迁移
npx drizzle-kit generate:pg
# 运行迁移(使用直接连接)
npx drizzle-kit push:pg
# 或使用自定义脚本
# src/db/migrate.ts
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const sql = postgres(process.env.DIRECT_URL!, { max: 1 });
const db = drizzle(sql);
await migrate(db, { migrationsFolder: "./drizzle" });
await sql.end();
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL") // 用于迁移
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// 创建
const user = await prisma.user.create({
data: {
name: "John Doe",
email: "john@example.com",
},
});
// 带关系的查询
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});
// 事务
await prisma.$transaction([
prisma.user.create({ data: { name: "User 1", email: "u1@example.com" } }),
prisma.user.create({ data: { name: "User 2", email: "u2@example.com" } }),
]);
# 创建迁移
npx prisma migrate dev --name init
# 部署到生产环境(使用 DIRECT_URL)
npx prisma migrate deploy
# 生成客户端
npx prisma generate
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false },
});
// 查询
const result = await pool.query("SELECT * FROM users WHERE email = $1", [
"john@example.com",
]);
// 事务
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", [
"John",
"john@example.com",
]);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
import { neon, neonConfig } from "@neondatabase/serverless";
// 为边缘运行时配置
neonConfig.fetchConnectionCache = true;
const sql = neon(process.env.DATABASE_URL!);
// 执行查询
const result = await sql`SELECT * FROM users WHERE email = ${email}`;
// 事务
const [user] = await sql.transaction([
sql`INSERT INTO users (name, email) VALUES (${name}, ${email}) RETURNING *`,
sql`INSERT INTO audit_log (action) VALUES ('user_created')`,
]);
# 通过 CLI
neonctl branches create --name dev --parent main
# 通过 API
curl -X POST https://console.neon.tech/api/v2/projects/{project_id}/branches \
-H "Authorization: Bearer $NEON_API_KEY" \
-d '{"name": "dev", "parent_id": "main"}'
# 通过控制台
# 导航到项目 → 分支 → 创建分支
# 1. 创建功能分支
neonctl branches create --name feature/user-auth --parent dev
# 2. 获取连接字符串
neonctl connection-string feature/user-auth
# 3. 更新 .env.local
DATABASE_URL="postgresql://...feature-user-auth..."
# 4. 运行迁移
npm run migrate
# 5. 开发和测试
# 6. 合并更改(通过模式迁移)
# 7. 删除分支
neonctl branches delete feature/user-auth
// vercel.json
{
"env": {
"DATABASE_URL": "@database-url-main"
},
"build": {
"env": {
"DATABASE_URL": "@database-url-preview"
}
}
}
// 在部署时创建预览分支
// .github/workflows/preview.yml
- name: Create Neon Branch
run: |
BRANCH_NAME="preview-${{ github.event.number }}"
neonctl branches create --name $BRANCH_NAME --parent main
DATABASE_URL=$(neonctl connection-string $BRANCH_NAME)
echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV
# 从特定时间戳创建分支
neonctl branches create --name recovery \
--parent main \
--timestamp "2024-01-15T10:30:00Z"
# 从分支恢复
neonctl branches reset main --from recovery
# 安装 Vercel CLI
npm i -g vercel
# 链接项目
vercel link
# 添加 Neon 集成
vercel integration add neon
# Vercel 自动:
# - 创建主分支连接
# - 为每个 PR 创建预览分支
# - 设置 DATABASE_URL 环境变量
# 添加到 Vercel 项目设置
vercel env add DATABASE_URL
# 用于预览分支
vercel env add DATABASE_URL preview
# 用于生产环境
vercel env add DATABASE_URL production
// app/api/users/route.ts
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET() {
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const sql = neon(process.env.DATABASE_URL!);
const [user] = await sql`SELECT * FROM users WHERE id = ${params.id}`;
if (!user) {
return new Response("Not found", { status: 404 });
}
return Response.json(user);
}
// 使用池化连接进行查询
const pooledDb = drizzle(neon(process.env.DATABASE_URL!));
// 使用直接连接进行迁移
const directDb = drizzle(neon(process.env.DIRECT_URL!));
// package.json 脚本
{
"scripts": {
"migrate": "DATABASE_URL=$DIRECT_URL drizzle-kit push:pg",
"dev": "next dev"
}
}
// 配置池大小
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // 最大连接数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// 对于无服务器,使用 Neon 的无服务器驱动
import { neon } from "@neondatabase/serverless";
// 自动处理连接池
# 免费层
- 0.25 计算单元 (CU)
- 空闲时缩容至零
- 共享计算
# 专业层
- 0.25 - 4 CU 自动扩缩容
- 可配置的最小/最大值
- 专用计算
# 通过 CLI
neonctl set-compute --min 0.25 --max 2 --branch main
# 通过 API
curl -X PATCH https://console.neon.tech/api/v2/projects/{id}/branches/{branch_id} \
-d '{"compute": {"min_cu": 0.25, "max_cu": 2}}'
// 开发:缩容至零
// min: 0.25 CU, max: 1 CU
// 预发布:最小基线
// min: 0.5 CU, max: 2 CU
// 生产:始终在线的基线
// min: 1 CU, max: 4 CU
// 按分支配置
const computeConfig = {
dev: { min: 0.25, max: 1 },
staging: { min: 0.5, max: 2 },
main: { min: 1, max: 4 },
};
# 创建只读副本
neonctl read-replica create --branch main --region us-east-1
# 获取连接字符串
neonctl connection-string --replica
// 写入主数据库
const writeDb = drizzle(neon(process.env.DATABASE_URL!));
// 从副本读取
const readDb = drizzle(neon(process.env.DATABASE_URL_REPLICA!));
// 应用逻辑
async function getUser(id: number) {
return await readDb.select().from(users).where(eq(users.id, id));
}
async function updateUser(id: number, data: any) {
return await writeDb.update(users).set(data).where(eq(users.id, id));
}
// 负载均衡
const replicas = [
process.env.DATABASE_URL_REPLICA_1!,
process.env.DATABASE_URL_REPLICA_2!,
];
function getReadConnection() {
const url = replicas[Math.floor(Math.random() * replicas.length)];
return drizzle(neon(url));
}
npm install -g neonctl
# 或使用 npx
npx neonctl --help
# 认证
neonctl auth
# 列出项目
neonctl projects list
# 创建项目
neonctl projects create --name my-app
# 列出分支
neonctl branches list
# 创建分支
neonctl branches create --name dev --parent main
# 获取连接字符串
neonctl connection-string main
# 数据库操作
neonctl databases create --name analytics
neonctl databases list
# 计算设置
neonctl set-compute --min 0.5 --max 2
# 删除分支
neonctl branches delete dev
// drizzle/migrate.ts
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const runMigrations = async () => {
const connection = postgres(process.env.DIRECT_URL!, { max: 1 });
const db = drizzle(connection);
console.log("Running migrations...");
await migrate(db, { migrationsFolder: "./drizzle" });
console.log("Migrations complete!");
await connection.end();
};
runMigrations();
# 开发
npx prisma migrate dev --name add_users_table
# 生产(使用 DIRECT_URL)
npx prisma migrate deploy
# 重置数据库(仅限开发)
npx prisma migrate reset
-- 1. 添加新列(可为空)
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);
-- 2. 回填数据
UPDATE users SET new_email = email;
-- 3. 设为非空(验证后)
ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;
-- 4. 删除旧列
ALTER TABLE users DROP COLUMN email;
-- 5. 重命名列
ALTER TABLE users RENAME COLUMN new_email TO email;
# 1. 创建迁移分支
neonctl branches create --name migration/add-index --parent main
# 2. 在分支上测试迁移
DATABASE_URL=$(neonctl connection-string migration/add-index) \
npm run migrate
# 3. 在分支上验证
DATABASE_URL=$(neonctl connection-string migration/add-index) \
npm run test
# 4. 应用到主分支
npm run migrate:production
# 5. 删除迁移分支
neonctl branches delete migration/add-index
// ✅ 为边缘运行时使用 Neon 无服务器驱动
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
// ✅ 启用连接缓存
import { neonConfig } from "@neondatabase/serverless";
neonConfig.fetchConnectionCache = true;
// ✅ 使用池化连接
const pooledUrl = process.env.DATABASE_URL; // -pooler 端点
// ❌ 不要在边缘运行时使用标准 pg
// import { Pool } from "pg"; // 在边缘运行时无效
// ✅ 在无服务器中重用连接
let cachedDb: ReturnType<typeof drizzle> | null = null;
function getDb() {
if (!cachedDb) {
const sql = neon(process.env.DATABASE_URL!);
cachedDb = drizzle(sql);
}
return cachedDb;
}
// ✅ 使用事务确保一致性
await db.transaction(async (tx) => {
await tx.insert(users).values({ name: "John" });
await tx.insert(auditLog).values({ action: "user_created" });
});
// ❌ 在长时间运行的进程中不要忘记关闭连接池
// await pool.end();
环境:
main: 生产数据
staging: 预生产测试
dev: 共享开发
feature/*: 独立功能
preview/*: PR 预览(自动创建)
生命周期:
- 在功能开始时从父分支创建
- 独立运行迁移
- 彻底测试
- 合并模式更改
- 功能完成后删除
// 开发:缩容至零
// - min_cu: 0.25
// - 空闲 5 分钟后暂停
// 预发布:最小始终在线
// - min_cu: 0.5
// - 在非高峰时段减少
// 生产:适当大小的基线
// - min_cu: 基于流量模式
// - max_cu: 处理峰值负载
// 分支清理
// PR 合并后删除未使用的预览分支
# Neon 连接字符串
DATABASE_URL="postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/db?sslmode=require"
DIRECT_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/db?sslmode=require"
# API 访问(用于 CLI/自动化)
NEON_API_KEY="your_api_key"
# 项目配置
NEON_PROJECT_ID="your_project_id"
# .env.local (开发)
DATABASE_URL="postgresql://...dev-branch..."
# .env.staging
DATABASE_URL="postgresql://...staging-branch..."
# .env.production (通过 Vercel)
DATABASE_URL="postgresql://...main-branch..."
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET() {
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT * FROM users ORDER BY created_at DESC LIMIT 10`;
return Response.json(users, {
headers: {
"Cache-Control": "s-maxage=60, stale-while-revalidate",
},
});
}
"use server";
import { neon } from "@neondatabase/serverless";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const sql = neon(process.env.DATABASE_URL!);
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
revalidatePath("/users");
}
async function testConnection() {
const sql = neon(process.env.DATABASE_URL!);
try {
const result = await sql`SELECT version()`;
console.log("✅ 已连接到 Neon:", result[0].version);
return true;
} catch (error) {
console.error("❌ 连接失败:", error);
return false;
}
}
// 检查 SSL 要求
const url = new URL(process.env.DATABASE_URL!);
if (!url.searchParams.has("sslmode")) {
url.searchParams.set("sslmode", "require");
}
// 验证端点类型
// -pooler: 用于应用查询
// direct: 用于迁移和管理任务
// 测试连接性
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
await sql`SELECT 1`; // 应该成功
# 使用直接连接进行迁移
export DIRECT_URL="postgresql://...direct-endpoint..."
npx prisma migrate deploy
# 检查迁移状态
npx prisma migrate status
# 强制重置(仅限开发)
npx prisma migrate reset
// 启用查询日志
import { drizzle } from "drizzle-orm/neon-http";
const db = drizzle(sql, { logger: true });
// 在 Neon 控制台中检查慢查询
// 监控 → 查询性能
// 添加索引
await sql`CREATE INDEX idx_users_email ON users(email)`;
// 使用连接池
// 确保使用 -pooler 端点
此技能全面涵盖了 Neon 无服务器 PostgreSQL,包括数据库分支、ORM 集成、无服务器部署模式和生产最佳实践。
每周安装次数
73
仓库
GitHub 星标数
18
首次出现
Jan 23, 2026
安全审计
安装于
codex55
gemini-cli54
cursor54
claude-code53
opencode53
github-copilot52
# Direct connection (for migrations, admin tasks)
DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"
# Pooled connection (for application queries)
DATABASE_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"
# PgBouncer pooled connection (recommended for serverless)
DATABASE_URL="postgresql://user:password@ep-xxx-pooler.region.aws.neon.tech/dbname?sslmode=require"
# Direct connection for migrations
DIRECT_URL="postgresql://user:password@ep-xxx.region.aws.neon.tech/dbname"
// drizzle.config.ts
import type { Config } from "drizzle-kit";
export default {
schema: "./src/db/schema.ts",
out: "./drizzle",
driver: "pg",
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
} satisfies Config;
// src/db/index.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);
// src/db/schema.ts
import { pgTable, serial, text, timestamp, varchar } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
createdAt: timestamp("created_at").defaultNow(),
});
export const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
userId: serial("user_id").references(() => users.id),
createdAt: timestamp("created_at").defaultNow(),
});
import { db } from "./db";
import { users, posts } from "./db/schema";
import { eq } from "drizzle-orm";
// Insert
const newUser = await db.insert(users).values({
name: "John Doe",
email: "john@example.com",
}).returning();
// Query
const allUsers = await db.select().from(users);
// Join
const userPosts = await db
.select()
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
// Update
await db.update(users)
.set({ name: "Jane Doe" })
.where(eq(users.id, 1));
# Generate migration
npx drizzle-kit generate:pg
# Run migration (use direct connection)
npx drizzle-kit push:pg
# Or use custom script
# src/db/migrate.ts
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const sql = postgres(process.env.DIRECT_URL!, { max: 1 });
const db = drizzle(sql);
await migrate(db, { migrationsFolder: "./drizzle" });
await sql.end();
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL") // For migrations
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// Create
const user = await prisma.user.create({
data: {
name: "John Doe",
email: "john@example.com",
},
});
// Query with relations
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});
// Transaction
await prisma.$transaction([
prisma.user.create({ data: { name: "User 1", email: "u1@example.com" } }),
prisma.user.create({ data: { name: "User 2", email: "u2@example.com" } }),
]);
# Create migration
npx prisma migrate dev --name init
# Deploy to production (uses DIRECT_URL)
npx prisma migrate deploy
# Generate client
npx prisma generate
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false },
});
// Query
const result = await pool.query("SELECT * FROM users WHERE email = $1", [
"john@example.com",
]);
// Transaction
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", [
"John",
"john@example.com",
]);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
import { neon, neonConfig } from "@neondatabase/serverless";
// Configure for edge runtime
neonConfig.fetchConnectionCache = true;
const sql = neon(process.env.DATABASE_URL!);
// Execute query
const result = await sql`SELECT * FROM users WHERE email = ${email}`;
// Transactions
const [user] = await sql.transaction([
sql`INSERT INTO users (name, email) VALUES (${name}, ${email}) RETURNING *`,
sql`INSERT INTO audit_log (action) VALUES ('user_created')`,
]);
# Via CLI
neonctl branches create --name dev --parent main
# Via API
curl -X POST https://console.neon.tech/api/v2/projects/{project_id}/branches \
-H "Authorization: Bearer $NEON_API_KEY" \
-d '{"name": "dev", "parent_id": "main"}'
# Via Console
# Navigate to project → Branches → Create branch
# 1. Create feature branch
neonctl branches create --name feature/user-auth --parent dev
# 2. Get connection string
neonctl connection-string feature/user-auth
# 3. Update .env.local
DATABASE_URL="postgresql://...feature-user-auth..."
# 4. Run migrations
npm run migrate
# 5. Develop and test
# 6. Merge changes (via schema migration)
# 7. Delete branch
neonctl branches delete feature/user-auth
// vercel.json
{
"env": {
"DATABASE_URL": "@database-url-main"
},
"build": {
"env": {
"DATABASE_URL": "@database-url-preview"
}
}
}
// Create preview branch on deploy
// .github/workflows/preview.yml
- name: Create Neon Branch
run: |
BRANCH_NAME="preview-${{ github.event.number }}"
neonctl branches create --name $BRANCH_NAME --parent main
DATABASE_URL=$(neonctl connection-string $BRANCH_NAME)
echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV
# Create branch from specific timestamp
neonctl branches create --name recovery \
--parent main \
--timestamp "2024-01-15T10:30:00Z"
# Restore from branch
neonctl branches reset main --from recovery
# Install Vercel CLI
npm i -g vercel
# Link project
vercel link
# Add Neon integration
vercel integration add neon
# Vercel automatically:
# - Creates main branch connection
# - Creates preview branch per PR
# - Sets DATABASE_URL environment variable
# Add to Vercel project settings
vercel env add DATABASE_URL
# For preview branches
vercel env add DATABASE_URL preview
# For production
vercel env add DATABASE_URL production
// app/api/users/route.ts
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET() {
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const sql = neon(process.env.DATABASE_URL!);
const [user] = await sql`SELECT * FROM users WHERE id = ${params.id}`;
if (!user) {
return new Response("Not found", { status: 404 });
}
return Response.json(user);
}
// Use pooled connection for queries
const pooledDb = drizzle(neon(process.env.DATABASE_URL!));
// Use direct connection for migrations
const directDb = drizzle(neon(process.env.DIRECT_URL!));
// package.json scripts
{
"scripts": {
"migrate": "DATABASE_URL=$DIRECT_URL drizzle-kit push:pg",
"dev": "next dev"
}
}
// Configure pool size
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Max connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// For serverless, use Neon's serverless driver
import { neon } from "@neondatabase/serverless";
// Automatically handles connection pooling
# Free Tier
- 0.25 Compute Units (CU)
- Scales to zero when idle
- Shared compute
# Pro Tier
- 0.25 - 4 CU autoscaling
- Configurable min/max
- Dedicated compute
# Via CLI
neonctl set-compute --min 0.25 --max 2 --branch main
# Via API
curl -X PATCH https://console.neon.tech/api/v2/projects/{id}/branches/{branch_id} \
-d '{"compute": {"min_cu": 0.25, "max_cu": 2}}'
// Development: Scale to zero
// min: 0.25 CU, max: 1 CU
// Staging: Minimal baseline
// min: 0.5 CU, max: 2 CU
// Production: Always-on baseline
// min: 1 CU, max: 4 CU
// Configure per branch
const computeConfig = {
dev: { min: 0.25, max: 1 },
staging: { min: 0.5, max: 2 },
main: { min: 1, max: 4 },
};
# Create read replica
neonctl read-replica create --branch main --region us-east-1
# Get connection string
neonctl connection-string --replica
// Write to primary
const writeDb = drizzle(neon(process.env.DATABASE_URL!));
// Read from replica
const readDb = drizzle(neon(process.env.DATABASE_URL_REPLICA!));
// Application logic
async function getUser(id: number) {
return await readDb.select().from(users).where(eq(users.id, id));
}
async function updateUser(id: number, data: any) {
return await writeDb.update(users).set(data).where(eq(users.id, id));
}
// Load balancing
const replicas = [
process.env.DATABASE_URL_REPLICA_1!,
process.env.DATABASE_URL_REPLICA_2!,
];
function getReadConnection() {
const url = replicas[Math.floor(Math.random() * replicas.length)];
return drizzle(neon(url));
}
npm install -g neonctl
# Or use npx
npx neonctl --help
# Authentication
neonctl auth
# List projects
neonctl projects list
# Create project
neonctl projects create --name my-app
# List branches
neonctl branches list
# Create branch
neonctl branches create --name dev --parent main
# Get connection string
neonctl connection-string main
# Database operations
neonctl databases create --name analytics
neonctl databases list
# Compute settings
neonctl set-compute --min 0.5 --max 2
# Delete branch
neonctl branches delete dev
// drizzle/migrate.ts
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const runMigrations = async () => {
const connection = postgres(process.env.DIRECT_URL!, { max: 1 });
const db = drizzle(connection);
console.log("Running migrations...");
await migrate(db, { migrationsFolder: "./drizzle" });
console.log("Migrations complete!");
await connection.end();
};
runMigrations();
# Development
npx prisma migrate dev --name add_users_table
# Production (uses DIRECT_URL)
npx prisma migrate deploy
# Reset database (dev only)
npx prisma migrate reset
-- 1. Add new column (nullable)
ALTER TABLE users ADD COLUMN new_email VARCHAR(255);
-- 2. Backfill data
UPDATE users SET new_email = email;
-- 3. Make non-nullable (after verification)
ALTER TABLE users ALTER COLUMN new_email SET NOT NULL;
-- 4. Drop old column
ALTER TABLE users DROP COLUMN email;
-- 5. Rename column
ALTER TABLE users RENAME COLUMN new_email TO email;
# 1. Create migration branch
neonctl branches create --name migration/add-index --parent main
# 2. Test migration on branch
DATABASE_URL=$(neonctl connection-string migration/add-index) \
npm run migrate
# 3. Verify on branch
DATABASE_URL=$(neonctl connection-string migration/add-index) \
npm run test
# 4. Apply to main
npm run migrate:production
# 5. Delete migration branch
neonctl branches delete migration/add-index
// ✅ Use Neon serverless driver for edge
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
// ✅ Enable connection caching
import { neonConfig } from "@neondatabase/serverless";
neonConfig.fetchConnectionCache = true;
// ✅ Use pooled connections
const pooledUrl = process.env.DATABASE_URL; // -pooler endpoint
// ❌ Don't use standard pg in edge runtime
// import { Pool } from "pg"; // Won't work in edge
// ✅ Reuse connections in serverless
let cachedDb: ReturnType<typeof drizzle> | null = null;
function getDb() {
if (!cachedDb) {
const sql = neon(process.env.DATABASE_URL!);
cachedDb = drizzle(sql);
}
return cachedDb;
}
// ✅ Use transactions for consistency
await db.transaction(async (tx) => {
await tx.insert(users).values({ name: "John" });
await tx.insert(auditLog).values({ action: "user_created" });
});
// ❌ Don't forget to close pools in long-running processes
// await pool.end();
Environments:
main: Production data
staging: Pre-production testing
dev: Shared development
feature/*: Individual features
preview/*: PR previews (auto-created)
Lifecycle:
- Create from parent on feature start
- Run migrations independently
- Test thoroughly
- Merge schema changes
- Delete after feature completion
// Development: Scale to zero
// - min_cu: 0.25
// - Suspend after 5 minutes idle
// Staging: Minimal always-on
// - min_cu: 0.5
// - Reduce during off-hours
// Production: Right-size baseline
// - min_cu: Based on traffic patterns
// - max_cu: Handle peak load
// Branch cleanup
// Delete unused preview branches after PR merge
# Neon connection strings
DATABASE_URL="postgresql://user:pass@ep-xxx-pooler.region.aws.neon.tech/db?sslmode=require"
DIRECT_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/db?sslmode=require"
# API access (for CLI/automation)
NEON_API_KEY="your_api_key"
# Project configuration
NEON_PROJECT_ID="your_project_id"
# .env.local (development)
DATABASE_URL="postgresql://...dev-branch..."
# .env.staging
DATABASE_URL="postgresql://...staging-branch..."
# .env.production (via Vercel)
DATABASE_URL="postgresql://...main-branch..."
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET() {
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT * FROM users ORDER BY created_at DESC LIMIT 10`;
return Response.json(users, {
headers: {
"Cache-Control": "s-maxage=60, stale-while-revalidate",
},
});
}
"use server";
import { neon } from "@neondatabase/serverless";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const sql = neon(process.env.DATABASE_URL!);
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
revalidatePath("/users");
}
async function testConnection() {
const sql = neon(process.env.DATABASE_URL!);
try {
const result = await sql`SELECT version()`;
console.log("✅ Connected to Neon:", result[0].version);
return true;
} catch (error) {
console.error("❌ Connection failed:", error);
return false;
}
}
// Check SSL requirement
const url = new URL(process.env.DATABASE_URL!);
if (!url.searchParams.has("sslmode")) {
url.searchParams.set("sslmode", "require");
}
// Verify endpoint type
// -pooler: For application queries
// direct: For migrations and admin tasks
// Test connectivity
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL!);
await sql`SELECT 1`; // Should succeed
# Use direct connection for migrations
export DIRECT_URL="postgresql://...direct-endpoint..."
npx prisma migrate deploy
# Check migration status
npx prisma migrate status
# Force reset (dev only)
npx prisma migrate reset
// Enable query logging
import { drizzle } from "drizzle-orm/neon-http";
const db = drizzle(sql, { logger: true });
// Check slow queries in Neon console
// Monitoring → Query Performance
// Add indexes
await sql`CREATE INDEX idx_users_email ON users(email)`;
// Use connection pooling
// Ensure using -pooler endpoint
This skill provides comprehensive coverage of Neon serverless Postgres, including database branching, ORM integrations, serverless deployment patterns, and production best practices.
Weekly Installs
73
Repository
GitHub Stars
18
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex55
gemini-cli54
cursor54
claude-code53
opencode53
github-copilot52