Loading...
Loading...
Compare original and translation side by side
@neondatabase/serverless@1.0.2@vercel/postgres@0.10.0drizzle-orm@0.44.7neonctl@2.16.1@neondatabase/serverless@1.0.2@vercel/postgres@0.10.0drizzle-orm@0.44.7neonctl@2.16.1npm install @neondatabase/serverlessnpm install @vercel/postgresnpm install @neondatabase/serverlessnpm install @vercel/postgresundefinedundefined
**For Vercel Postgres:**
```bash
**使用Vercel Postgres**:
```bash
**CRITICAL:**
- Use **pooled connection string** for serverless (ends with `-pooler.region.aws.neon.tech`)
- Non-pooled connections will exhaust quickly in serverless environments
- Always include `?sslmode=require` parameter
**关键注意事项**:
- 无服务器环境请使用**带连接池的连接字符串**(主机名以`-pooler.region.aws.neon.tech`结尾)
- 不带连接池的连接会在无服务器环境中快速耗尽
- 务必包含`?sslmode=require`参数import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const result = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`SELECT * FROM users WHERE name = ${name}`
]);import { sql } from '@vercel/postgres';
// Simple query
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const client = await sql.connect();
try {
await client.sql`BEGIN`;
await client.sql`INSERT INTO users (name) VALUES (${name})`;
await client.sql`COMMIT`;
} finally {
client.release();
}sql`...`sql('SELECT * FROM users WHERE id = ' + id)import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// 简单查询
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// 事务
const result = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`SELECT * FROM users WHERE name = ${name}`
]);import { sql } from '@vercel/postgres';
// 简单查询
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// 事务
const client = await sql.connect();
try {
await client.sql`BEGIN`;
await client.sql`INSERT INTO users (name) VALUES (${name})`;
await client.sql`COMMIT`;
} finally {
client.release();
}sql`...`sql('SELECT * FROM users WHERE id = ' + id)npm install @neondatabase/serverlessnpm install @vercel/postgresundefinednpm install @neondatabase/serverlessnpm install @vercel/postgresundefined
**Key Points:**
- Both packages use HTTP/WebSocket (no TCP required)
- Edge-compatible (works in Cloudflare Workers, Vercel Edge Runtime)
- Connection pooling is built-in when using pooled connection strings
- No need for separate connection pool libraries
---
**核心要点**:
- 两个包均使用HTTP/WebSocket协议(无需TCP)
- 兼容边缘环境(可在Cloudflare Workers、Vercel Edge Runtime中运行)
- 使用带连接池的连接字符串时,内置连接池功能
- 无需额外的连接池库
---postgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db?sslmode=requirevercel env pullundefinedpostgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db?sslmode=requirevercel env pullundefined
**CRITICAL:**
- Always use the **pooled connection string** (ends with `-pooler.region.aws.neon.tech`)
- Non-pooled connections are for direct connections (not serverless)
- Include `?sslmode=require` in connection string
---
**关键注意事项**:
- 始终使用**带连接池的连接字符串**(主机名以`-pooler.region.aws.neon.tech`结尾)
- 不带连接池的连接适用于直接连接场景(非无服务器环境)
- 连接字符串中务必包含`?sslmode=require`
---undefinedundefined
**For Vercel Postgres:**
```bash
**Vercel Postgres**:
```bashvercel env pullvercel env pull
**For Cloudflare Workers** (wrangler.jsonc):
```json
{
"vars": {
"DATABASE_URL": "postgresql://user:password@ep-xyz-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
}
}POSTGRES_URLPOSTGRES_PRISMA_URLPOSTGRES_URL_NON_POOLING
**Cloudflare Workers**(wrangler.jsonc):
```json
{
"vars": {
"DATABASE_URL": "postgresql://user:password@ep-xyz-pooler.us-east-1.aws.neon.tech/neondb?sslmode=require"
}
}POSTGRES_URLPOSTGRES_PRISMA_URLPOSTGRES_URL_NON_POOLING// scripts/migrate.ts
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;// db/schema.ts
import { 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(),
createdAt: timestamp('created_at').defaultNow()
});// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });undefined// scripts/migrate.ts
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;// db/schema.ts
import { 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(),
createdAt: timestamp('created_at').defaultNow()
});// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });undefined
**Option C: Prisma**
```prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}npx prisma migrate dev --name init
**选项C:Prisma**
```prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}npx prisma migrate dev --name initimport { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// SELECT
const users = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const newUser = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// UPDATE
await sql`UPDATE users SET name = ${newName} WHERE id = ${id}`;
// DELETE
await sql`DELETE FROM users WHERE id = ${id}`;import { sql } from '@vercel/postgres';
// SELECT
const { rows } = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const { rows: newUser } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;// Automatic transaction
const results = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}`
]);
// Manual transaction (for complex logic)
const result = await sql.transaction(async (sql) => {
const [user] = await sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await sql`INSERT INTO profiles (user_id) VALUES (${user.id})`;
return user;
});import { sql } from '@vercel/postgres';
const client = await sql.connect();
try {
await client.sql`BEGIN`;
const { rows } = await client.sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await client.sql`INSERT INTO profiles (user_id) VALUES (${rows[0].id})`;
await client.sql`COMMIT`;
} catch (e) {
await client.sql`ROLLBACK`;
throw e;
} finally {
client.release();
}import { db } from './db';
import { users } from './db/schema';
import { eq } from 'drizzle-orm';
// SELECT
const allUsers = await db.select().from(users);
const user = await db.select().from(users).where(eq(users.email, email));
// INSERT
const newUser = await db.insert(users).values({ name, email }).returning();
// UPDATE
await db.update(users).set({ name: newName }).where(eq(users.id, id));
// DELETE
await db.delete(users).where(eq(users.id, id));
// Transactions
await db.transaction(async (tx) => {
await tx.insert(users).values({ name, email });
await tx.insert(profiles).values({ userId: user.id });
});sql`...`import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// 查询
const users = await sql`SELECT * FROM users WHERE email = ${email}`;
// 插入
const newUser = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// 更新
await sql`UPDATE users SET name = ${newName} WHERE id = ${id}`;
// 删除
await sql`DELETE FROM users WHERE id = ${id}`;import { sql } from '@vercel/postgres';
// 查询
const { rows } = await sql`SELECT * FROM users WHERE email = ${email}`;
// 插入
const { rows: newUser } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;// 自动事务
const results = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}`
]);
// 手动事务(复杂逻辑场景)
const result = await sql.transaction(async (sql) => {
const [user] = await sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await sql`INSERT INTO profiles (user_id) VALUES (${user.id})`;
return user;
});import { sql } from '@vercel/postgres';
const client = await sql.connect();
try {
await client.sql`BEGIN`;
const { rows } = await client.sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await client.sql`INSERT INTO profiles (user_id) VALUES (${rows[0].id})`;
await client.sql`COMMIT`;
} catch (e) {
await client.sql`ROLLBACK`;
throw e;
} finally {
client.release();
}import { db } from './db';
import { users } from './db/schema';
import { eq } from 'drizzle-orm';
// 查询
const allUsers = await db.select().from(users);
const user = await db.select().from(users).where(eq(users.email, email));
// 插入
const newUser = await db.insert(users).values({ name, email }).returning();
// 更新
await db.update(users).set({ name: newName }).where(eq(users.id, id));
// 删除
await db.delete(users).where(eq(users.id, id));
// 事务
await db.transaction(async (tx) => {
await tx.insert(users).values({ name, email });
await tx.insert(profiles).values({ userId: user.id });
});sql`...`Pooled (serverless): postgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db
Non-pooled (direct): postgresql://user:pass@ep-xyz.region.aws.neon.tech/db-pooler.// Both packages handle pooling automatically when using pooled connection string
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!); // Pooling is automatic带连接池(无服务器): postgresql://user:pass@ep-xyz-pooler.region.aws.neon.tech/db
不带连接池(直接连接): postgresql://user:pass@ep-xyz.region.aws.neon.tech/db-pooler.// 使用带连接池的连接字符串时,两个包均自动处理连接池
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!); // 自动启用连接池// src/index.ts
import { neon } from '@neondatabase/serverless';
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};undefined// src/index.ts
import { neon } from '@neondatabase/serverless';
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};undefined
**Vercel (Next.js API Route):**
```typescript
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json(rows);
}undefined
**Vercel(Next.js API路由)**:
```typescript
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json(rows);
}undefined
**Test Queries:**
```bash
**测试查询**:
```bash
**Key Points:**
- Test locally before deploying
- Monitor query performance in Neon dashboard
- Set up alerts for connection pool exhaustion
- Use Neon's query history for debugging
---
**核心要点**:
- 部署前先在本地测试
- 在Neon控制台监控查询性能
- 为连接池耗尽设置告警
- 使用Neon的查询历史进行调试
----pooler.sql`SELECT * FROM users`sslmode=require-pooler.sql`SELECT * FROM users`sslmode=require'SELECT * FROM users WHERE id = ' + idsslmode=requireclient.release().env.gitignorePOSTGRES_URL_NON_POOLING'SELECT * FROM users WHERE id = ' + idsslmode=requireclient.release().env.gitignorePOSTGRES_URL_NON_POOLINGError: connection pool exhaustedtoo many connections for role-pooler.Error: connection pool exhaustedtoo many connections for role-pooler.Error: TCP connections are not supported in this environment@neondatabase/serverlesspgpostgres.jsError: TCP connections are not supported in this environment@neondatabase/serverlesspgpostgres.jssql('SELECT * FROM users WHERE id = ' + id)sql`SELECT * FROM users WHERE id = ${id}`sql('SELECT * FROM users WHERE id = ' + id)sql`SELECT * FROM users WHERE id = ${id}`Error: connection requires SSLFATAL: no pg_hba.conf entry?sslmode=require?sslmode=requireError: connection requires SSLFATAL: no pg_hba.conf entry?sslmode=require?sslmode=requireclient.release()client.release()client.release()client.release()Error: Connection string is undefinedconnect ECONNREFUSEDDATABASE_URLPOSTGRES_URLPOSTGRES_URLPOSTGRES_PRISMA_URLError: Connection string is undefinedconnect ECONNREFUSEDDATABASE_URLPOSTGRES_URLPOSTGRES_URLPOSTGRES_PRISMA_URLError: Query timeoutError: transaction timeoutError: Query timeoutError: transaction timeoutError: PrismaClient is unable to be run in the browserError: PrismaClient is unable to be run in the browserError: UnauthorizedNEON_API_KEYError: UnauthorizedNEON_API_KEYError: database "xyz" does not existDATABASE_URLError: database "xyz" does not existDATABASE_URLError: Query timeoutError: Query timeoutProperty 'x' does not exist on type 'User'npx drizzle-kit generateProperty 'x' does not exist on type 'User'npx drizzle-kit generateError: relation "xyz" already existsError: relation "xyz" already existsError: timestamp is outside retention windowError: timestamp is outside retention windowError: Invalid connection string@prisma/adapter-neon@prisma/adapter-neon@neondatabase/serverlessError: Invalid connection string@prisma/adapter-neon@prisma/adapter-neon@neondatabase/serverless{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}{
"dependencies": {
"@vercel/postgres": "^0.10.0"
}
}{
"dependencies": {
"@vercel/postgres": "^0.10.0"
}
}{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});@neondatabase/serverless@vercel/postgresdrizzle-ormdrizzle-kitimport { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});@neondatabase/serverless@vercel/postgresdrizzle-ormdrizzle-kit// src/index.ts
import { neon } from '@neondatabase/serverless';
interface Env {
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
// Parse request
const url = new URL(request.url);
if (url.pathname === '/api/users' && request.method === 'GET') {
const users = await sql`SELECT id, name, email FROM users`;
return Response.json(users);
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const { name, email } = await request.json();
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
return Response.json(user, { status: 201 });
}
return new Response('Not Found', { status: 404 });
}
};// src/index.ts
import { neon } from '@neondatabase/serverless';
interface Env {
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
// 解析请求
const url = new URL(request.url);
if (url.pathname === '/api/users' && request.method === 'GET') {
const users = await sql`SELECT id, name, email FROM users`;
return Response.json(users);
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const { name, email } = await request.json();
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
return Response.json(user, { status: 201 });
}
return new Response('Not Found', { status: 404 });
}
};// app/actions/users.ts
'use server';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
export async function getUsers() {
const { rows } = await sql`SELECT id, name, email FROM users ORDER BY created_at DESC`;
return rows;
}
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const { rows } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
revalidatePath('/users');
return rows[0];
}
export async function deleteUser(id: number) {
await sql`DELETE FROM users WHERE id = ${id}`;
revalidatePath('/users');
}// app/actions/users.ts
'use server';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
export async function getUsers() {
const { rows } = await sql`SELECT id, name, email FROM users ORDER BY created_at DESC`;
return rows;
}
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const { rows } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
revalidatePath('/users');
return rows[0];
}
export async function deleteUser(id: number) {
await sql`DELETE FROM users WHERE id = ${id}`;
revalidatePath('/users');
}// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: timestamp('created_at').defaultNow()
});// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });// app/api/posts/route.ts
import { db } from '@/db';
import { posts, users } from '@/db/schema';
import { eq } from 'drizzle-orm';
export async function GET() {
// Type-safe query with joins
const postsWithAuthors = await db
.select({
postId: posts.id,
title: posts.title,
content: posts.content,
authorName: users.name
})
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
return Response.json(postsWithAuthors);
}// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: timestamp('created_at').defaultNow()
});// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });// app/api/posts/route.ts
import { db } from '@/db';
import { posts, users } from '@/db/schema';
import { eq } from 'drizzle-orm';
export async function GET() {
// 类型安全的关联查询
const postsWithAuthors = await db
.select({
postId: posts.id,
title: posts.title,
content: posts.content,
authorName: users.name
})
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
return Response.json(postsWithAuthors);
}// Neon Direct - Automatic Transaction
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const result = await sql.transaction(async (tx) => {
// Deduct from sender
const [sender] = await tx`
UPDATE accounts
SET balance = balance - ${amount}
WHERE id = ${senderId} AND balance >= ${amount}
RETURNING *
`;
if (!sender) {
throw new Error('Insufficient funds');
}
// Add to recipient
await tx`
UPDATE accounts
SET balance = balance + ${amount}
WHERE id = ${recipientId}
`;
// Log transaction
await tx`
INSERT INTO transfers (from_id, to_id, amount)
VALUES (${senderId}, ${recipientId}, ${amount})
`;
return sender;
});// Neon Direct - 自动事务
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const result = await sql.transaction(async (tx) => {
// 从付款方扣款
const [sender] = await tx`
UPDATE accounts
SET balance = balance - ${amount}
WHERE id = ${senderId} AND balance >= ${amount}
RETURNING *
`;
if (!sender) {
throw new Error('余额不足');
}
// 向收款方转账
await tx`
UPDATE accounts
SET balance = balance + ${amount}
WHERE id = ${recipientId}
`;
// 记录交易
await tx`
INSERT INTO transfers (from_id, to_id, amount)
VALUES (${senderId}, ${recipientId}, ${amount})
`;
return sender;
});undefinedundefined
```yaml
```yaml - name: Deploy to Vercel
env:
DATABASE_URL: ${{ steps.branch.outputs.url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URL
**When to use**: Want isolated database for each PR/preview deployment
--- - name: 部署到Vercel
env:
DATABASE_URL: ${{ steps.branch.outputs.url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URL
**适用场景**:为每个PR/预览部署提供独立数据库的场景
---chmod +x scripts/setup-neon.sh
./scripts/setup-neon.sh my-project-namenpx tsx scripts/test-connection.tschmod +x scripts/setup-neon.sh
./scripts/setup-neon.sh my-project-namenpx tsx scripts/test-connection.tsreferences/connection-strings.mdreferences/drizzle-setup.mdreferences/prisma-setup.mdreferences/branching-guide.mdreferences/migration-strategies.mdreferences/common-errors.mdconnection-strings.mddrizzle-setup.mdprisma-setup.mdbranching-guide.mdcommon-errors.mdreferences/connection-strings.mdreferences/drizzle-setup.mdreferences/prisma-setup.mdreferences/branching-guide.mdreferences/migration-strategies.mdreferences/common-errors.mdconnection-strings.mddrizzle-setup.mdprisma-setup.mdbranching-guide.mdcommon-errors.mdassets/schema-example.sqlassets/drizzle-schema.tsassets/prisma-schema.prismaassets/schema-example.sqlassets/drizzle-schema.tsassets/prisma-schema.prismaundefinedundefined
**Branch Management:**
```bash
**分支管理**:
```bash
**Use Cases:**
- **Preview deployments**: Create branch per PR, delete on merge
- **Testing**: Create branch, run tests, delete
- **Debugging**: Create branch from production at specific timestamp
- **Development**: Separate dev/staging/prod branches
**CRITICAL:**
- Branches share compute limits on free tier
- Each branch can have independent compute settings (paid plans)
- Data changes are copy-on-write (instant, no copying)
- Retention period applies to all branches
---
**适用场景**:
- **预览部署**:为每个PR创建分支,合并后删除
- **测试**:创建分支,运行测试,删除分支
- **调试**:从生产环境特定时间点创建分支
- **开发**:独立的开发/预发布/生产分支
**关键注意事项**:
- 免费版分支共享计算资源限制
- 付费计划中每个分支可配置独立的计算设置
- 数据变更为写时复制(即时完成,无需复制数据)
- 保留期适用于所有分支
---| Feature | Pooled ( | Non-Pooled |
|---|---|---|
| Use Case | Serverless, edge functions | Long-running servers |
| Max Connections | ~10,000 (shared) | ~100 (per database) |
| Connection Reuse | Yes | No |
| Latency | +1-2ms overhead | Direct |
| Idle Timeout | 60s | Configurable |
Error: connection pool exhausted| 特性 | 带连接池( | 不带连接池 |
|---|---|---|
| 适用场景 | 无服务器、边缘函数 | 长期运行的服务器 |
| 最大连接数 | ~10,000(共享) | ~100(每个数据库) |
| 连接复用 | 是 | 否 |
| 延迟 | +1-2ms 开销 | 直接连接 |
| 空闲超时 | 60秒 | 可配置 |
Error: connection pool exhaustedconst result = await sql`
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = ${email}
`;await sql`CREATE INDEX idx_users_email ON users(email)`;
await sql`CREATE INDEX idx_posts_user_id ON posts(user_id)`;import { pgTable, serial, text, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique()
}, (table) => ({
emailIdx: index('email_idx').on(table.email)
}));// ❌ Bad: N+1 queries
for (const user of users) {
const posts = await sql`SELECT * FROM posts WHERE user_id = ${user.id}`;
}
// ✅ Good: Single query with JOIN
const postsWithUsers = await sql`
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
`;const getUserByEmail = db.select().from(users).where(eq(users.email, sql.placeholder('email'))).prepare('get_user_by_email');
// Reuse prepared statement
const user1 = await getUserByEmail.execute({ email: 'alice@example.com' });
const user2 = await getUserByEmail.execute({ email: 'bob@example.com' });const result = await sql`
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = ${email}
`;await sql`CREATE INDEX idx_users_email ON users(email)`;
await sql`CREATE INDEX idx_posts_user_id ON posts(user_id)`;import { pgTable, serial, text, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique()
}, (table) => ({
emailIdx: index('email_idx').on(table.email)
}));// ❌ 不佳:N+1查询
for (const user of users) {
const posts = await sql`SELECT * FROM posts WHERE user_id = ${user.id}`;
}
// ✅ 良好:单查询关联
const postsWithUsers = await sql`
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
`;const getUserByEmail = db.select().from(users).where(eq(users.email, sql.placeholder('email'))).prepare('get_user_by_email');
// 复用预编译语句
const user1 = await getUserByEmail.execute({ email: 'alice@example.com' });
const user2 = await getUserByEmail.execute({ email: 'bob@example.com' });// ❌ Bad
const sql = neon('postgresql://user:pass@host/db');
// ✅ Good
const sql = neon(process.env.DATABASE_URL!);-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY "Users can only see their own posts"
ON posts
FOR SELECT
USING (user_id = current_user_id());// ✅ Validate before query
const emailSchema = z.string().email();
const email = emailSchema.parse(input.email);
const user = await sql`SELECT * FROM users WHERE email = ${email}`;// ✅ Always paginate
const page = Math.max(1, parseInt(request.query.page));
const limit = 50;
const offset = (page - 1) * limit;
const users = await sql`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;// ❌ 错误
const sql = neon('postgresql://user:pass@host/db');
// ✅ 正确
const sql = neon(process.env.DATABASE_URL!);-- 启用RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 创建策略
CREATE POLICY "用户仅可查看自己的文章"
ON posts
FOR SELECT
USING (user_id = current_user_id());// ✅ 查询前验证
const emailSchema = z.string().email();
const email = emailSchema.parse(input.email);
const user = await sql`SELECT * FROM users WHERE email = ${email}`;// ✅ 始终分页
const page = Math.max(1, parseInt(request.query.page));
const limit = 50;
const offset = (page - 1) * limit;
const users = await sql`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;@neondatabase/serverless@^1.0.2@vercel/postgres@^0.10.0drizzle-orm@^0.44.7drizzle-kit@^0.31.0@prisma/client@^6.10.0@prisma/adapter-neon@^6.10.0neonctl@^2.16.1zod@^3.24.0@neondatabase/serverless@^1.0.2@vercel/postgres@^0.10.0drizzle-orm@^0.44.7drizzle-kit@^0.31.0@prisma/client@^6.10.0@prisma/adapter-neon@^6.10.0neonctl@^2.16.1zod@^3.24.0/github/neondatabase/serverless/github/vercel/storage/github/neondatabase/serverless/github/vercel/storage{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0",
"neonctl": "^2.16.1"
}
}{
"dependencies": {
"@prisma/client": "^6.10.0",
"@prisma/adapter-neon": "^6.10.0"
},
"devDependencies": {
"prisma": "^6.10.0"
}
}{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0",
"neonctl": "^2.16.1"
}
}{
"dependencies": {
"@prisma/client": "^6.10.0",
"@prisma/adapter-neon": "^6.10.0"
},
"devDependencies": {
"prisma": "^6.10.0"
}
}Error: connection pool exhaustedError: connection pool exhausted-pooler.region.aws.neon.tech-pooler.region.aws.neon.techError: TCP connections are not supportedError: TCP connections are not supported@neondatabase/serverlesspgpostgres.js@neondatabase/serverlesspgpostgres.jsError: database "xyz" does not existError: database "xyz" does not existDATABASE_URLDATABASE_URLPrismaClient is unable to be run in the browserPrismaClient is unable to be run in the browser@prisma/adapter-neon@prisma/adapter-neonneonctl branches reset feature --parent mainneonctl branches reset feature --parent main@neondatabase/serverless@vercel/postgres-pooler.?sslmode=requireDATABASE_URLPOSTGRES_URLsql`...`references/common-errors.mdsslmode=requirescripts/test-connection.ts@neondatabase/serverless@vercel/postgres-pooler.?sslmode=requireDATABASE_URLPOSTGRES_URLsql`...`references/common-errors.mdsslmode=requirescripts/test-connection.ts