关于 netlify-database
介绍如何在 Netlify 项目中使用托管 Postgres 数据库,涵盖查询、迁移、Drizzle 集成、本地开发和数据库 CLI 工作流。
主要功能
- 01托管 Postgres 配置
- 02参数化 SQL 查询
- 03生成并部署数据库迁移
- 04Drizzle ORM 适配器说明
- 05本地数据库与 CLI 工作流
适用场景
- 01为 Netlify 项目添加 Postgres
- 02从 Functions 或构建任务查询数据
- 03通过迁移管理架构变更
- 04使用本地数据库进行开发和测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ netlify/ context-and-tools --skill netlify-database安装后在 Claude Code 中输入/netlify-database确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cli-commands.mdlegacy-extension.mdlocal-dev.mdmigration-from-extension.mdmigrations.mdoperational-footguns.md详细介绍
Netlify Database
零配置托管 Postgres。安装 @netlify/database,在 netlify/database/migrations/ 下编写迁移,然后部署——Netlify 会自动配置数据库并应用迁移。可从 Functions、Edge Functions、Builds 和 Agent Runners 查询。
现代客户端(优先使用此方式)
import { getDatabase } from "@netlify/database";
const db = getDatabase(); // auto-selects connection for the runtime
const userId = 42;
const users = await db.sql`SELECT * FROM users WHERE id = ${userId}`; // auto-parameterized
改用自有驱动程序/ORM:
import { getConnectionString } from "@netlify/database";
const connectionString = getConnectionString(); // correct branch for this env
旧版 — 新代码中请勿使用: import { neon } from "@netlify/neon"。已由 @netlify/database 取代。将 neon() 调用替换为 Drizzle netlify-db 适配器,或通过 getConnectionString() 使用 Postgres 驱动程序。旧环境变量 NETLIFY_DATABASE_URL 已替换为 NETLIFY_DB_URL。
文件放置位置
| 内容 | 位置 |
|---|---|
| 迁移 | netlify/database/migrations/(SQL 文件,或包含 migration.sql 的子目录) |
| 查询代码 | Functions(netlify/functions/)、Edge Functions |
| Drizzle 架构 | db/schema.ts(惯例) |
| Drizzle 客户端 | db/index.ts(惯例) |
| 连接字符串 | NETLIFY_DB_URL 环境变量,或 getConnectionString() |
查询
getDatabase(options?) 会返回包含 sql 和 pool 的客户端。options.connectionString 会覆盖自动配置的连接字符串;options.debug 会启用日志记录。
const db = getDatabase();
const active = await db.sql`SELECT * FROM users WHERE active = ${true}`;
await db.sql`INSERT INTO users (name, email) VALUES (${"Ada"}, ${"[email protected]"})`;
await db.sql`UPDATE users SET name = ${"Ada Lovelace"} WHERE id = ${1}`;
await db.sql`DELETE FROM users WHERE id = ${1}`;
// Type the rows
interface User { id: number; name: string; email: string; }
const typed = await db.sql<User>`SELECT * FROM users`;
// Stream
for await (const row of db.sql`SELECT * FROM users`.stream()) { /* ... */ }
for await (const chunk of db.sql`SELECT * FROM users`.chunked(100)) { /* ... */ }
SQLTemplate 方法:execute() → Promise<T[]>,stream() → AsyncGenerator<T>,chunked(n) → AsyncGenerator<T[]>,toSQL() → 返回原始 SQL 和参数,但不执行查询。
sql 辅助项:
sql.identifier(value)— 安全的表名/列名。可接受字符串、string[] 或{ schema, table, column, as }。sql.values(rows)— 根据二维数组生成批量插入值列表。sql.default— SQLDEFAULT关键字。sql.raw(value)— 注入未参数化的 SQL;会绕过注入保护。仅用于可信常量(例如"DESC"),绝不可用于用户输入。sql.unsafe(query, params?, { rowMode })— 使用$1参数的原始查询字符串;rowMode为"array"或"object"。
事务 — 使用 pool
db.pool 是 pg.Pool。BEGIN/查询/COMMIT 必须在同一连接上运行:
const client = await db.pool.connect();
try {
await client.query("BEGIN");
await client.query("INSERT INTO users (name, email) VALUES ($1, $2)", ["Ada", "[email protected]"]);
await client.query("INSERT INTO posts (author_id, title) VALUES ($1, $2)", [1, "First post"]);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
自有驱动程序:
import { getConnectionString } from "@netlify/database";
import pg from "pg";
const pool = new pg.Pool({ connectionString: getConnectionString() });
// or the `postgres` driver via env var
import postgres from "postgres";
const sql = postgres(process.env.NETLIFY_DB_URL);
Drizzle ORM
必须从 @beta 安装这两个包。 latest 不包含 drizzle-orm/netlify-db 适配器,因此会失败。
npm install @netlify/database drizzle-orm@beta
npm install -D drizzle-kit@beta
drizzle.config.ts — 你必须将 out 设置为 Netlify 迁移目录,否则 Netlify 不会应用生成的迁移:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./db/schema.ts",
out: "netlify/database/migrations", // NOT the default "drizzle"
});
import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: serial().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
createdAt: timestamp().defaultNow(),
});
import { drizzle } from "drizzle-orm/netlify-db"; // native adapter, auto-configured
import * as schema from "./schema";
export const db = drizzle({ schema });
import { desc } from "drizzle-orm";
import type { Config, Context } from "@netlify/functions";
import { db } from "../../db";
import { users } from "../../db/schema";
export default async (req: Request, context: Context) => {
if (req.method === "GET") {
const allUsers = await db.select().from(users).orderBy(desc(users.createdAt));
return Response.json(allUsers);
}
if (req.method === "POST") {
const { name, email } = await req.json();
const [user] = await db.insert(users).values({ name, email }).returning();
return Response.json(user, { status: 201 });
}
return new Response("Method not allowed", { status: 405 });
};
export const config: Config = { path: "/api/users" };
编辑架构后生成迁移:npx drizzle-kit generate。
绝不要对 Netlify 托管的数据库运行 drizzle-kit push,也绝不要对 NETLIFY_DB_URL 运行 drizzle-kit migrate。 架构只能通过已提交的迁移文件部署到托管数据库,并由部署过程应用。generate 会写入文件;部署过程会应用这些文件。
迁移
文件位于 netlify/database/migrations/。有两种格式:
netlify/database/migrations/20260301143000_create_users.sql # single SQL file
netlify/database/migrations/20260318091500_add_posts/migration.sql # subdir form
命名规则:<number>_<slug>——number 是数字(时间戳或 0001…),用于定义顺序;slug 由小写字母、数字、连字符和下划线组成。按字典序排序,并按顺序应用。使用时间戳前缀(netlify database migrations new 会自动处理),以避免因顺序错乱而被拒绝。
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
post_id INTEGER NOT NULL REFERENCES posts(id),
author_id INTEGER NOT NULL REFERENCES users(id),
body TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
应用时机:
- 生产部署:在发布前立即应用;如果失败,则阻止发布。关闭自动发布时,Netlify 会等待手动发布后再应用。
- 部署预览:每次部署上线前都会应用;如果失败,则部署失败。
- 本地:不会自动应用——请自行运行
netlify database migrations apply。
迁移易错点(均会被检测为偏移 / 拒绝):
- 绝不要编辑已应用的迁移——校验和偏移:
migration "<name>" has been modified after being applied。请编写新的修正迁移。 - 绝不要删除已应用的迁移——
... has been removed after being applied。请将其恢复。 - **顺序错乱:**前缀 ≤ 最高已应用版本时会被拒绝。使用时间戳可避免此问题。
- 优先使用向后兼容的迁移。破坏性变更(重命名 / 删除列)→ 跨多个部署采用扩展-收缩模式。新建表 / 可空列 → 使用单个迁移即可。
自带迁移系统:选择 netlify/database/migrations 以外的目录,避免自动检测;并由你负责将迁移应用到预览分支和生产环境。
请参阅 references/migrations.md。
本地开发
本地只有一个数据库,所有代码都以它为目标——分支是部署时的概念,在本地并不存在。它是与生产环境镜像一致的真实 Postgres 兼容引擎,但以单进程运行(不适用于负载测试);自动扩缩容 / 休眠设置不适用。
启动它——任选一种方式,状态可互换:
netlify dev # CLI starts + tears down the local DB
或者使用 Vite 插件:
import { defineConfig } from "vite";
import netlify from "@netlify/vite-plugin";
export default defineConfig({ plugins: [netlify()] });
常用命令(本地数据库运行期间):
netlify database migrations apply # apply pending locally
netlify database migrations new -d "add users table" # scaffold new migration
netlify database migrations pull # overwrite local migrations from remote
netlify database status # enabled? installed? applied/pending migrations
netlify database connect # interactive SQL REPL
netlify database connect --query "SELECT * FROM users LIMIT 10"
netlify database reset # drop all schemas/tables — LOCAL ONLY
netlify database migrations reset # delete unapplied local migration files
外部工具(netlify dev 运行期间可用):
psql "$(netlify database connect --json | jq -r .connection_string)"
请参阅 references/local-dev.md。
设置
新项目:在 https://app.netlify.com/start 向 Agent Runners 描述你的应用,或在本地运行 netlify create "<description>"。
现有项目:
netlify database init # installs @netlify/database, picks Drizzle or raw SQL, scaffolds a migration
netlify database init --yes # non-interactive (CI / agents)
netlify dev
手动设置:运行 npm install @netlify/database,在 netlify/database/migrations/ 下编写迁移,编写函数,运行 netlify dev,然后部署。
如果未安装 @netlify/database,Netlify 将不会自动配置数据库——你必须通过 UI 中的 Data & Storage > Database 菜单手动创建数据库。请安装此软件包。
CLI 参考(netlify database)
前置条件:Node ≥ 20.12.2、Netlify CLI ≥ 26.0.0(npm install -g netlify-cli)。所有命令都支持 --json。
| 命令 | 用途 | 关键标志 |
|---|---|---|
init | 在项目中设置数据库 | -y, --yes |
status | 状态:已启用、已安装、连接字符串、已应用 / 待处理的迁移 | -b, --branch、--show-credentials |
connect | SQL REPL,或使用 --query 执行一次性查询 | -q, --query、--json |
migrations apply | 将待处理迁移应用到本地数据库 | --to <name> |
migrations new | 创建迁移脚手架 | -d, --description、-s, --scheme sequential|timestamp |
migrations pull | 用分支中的文件覆盖本地文件 | -b, --branch、--force |
migrations reset | 删除尚未应用的本地迁移文件 | -b, --branch |
reset | 删除所有数据 / 表——仅限本地 | — |
请参阅 references/cli-commands.md。
REST API
限定于某个站点,根地址为 https://api.netlify.com/api/v1,OAuth 2。完整参考:https://open-api.netlify.com。
| 方法 + 路径 | 用途 |
|---|---|
POST /sites/{site_id}/database | 创建数据库(如果已存在,则返回现有连接字符串);region 可选 |
GET /sites/{site_id}/database | 获取连接字符串 |
POST /sites/{site_id}/database/branch | 创建分支;请求体为 deploy_id(必需)、parent_branch_id(可选,默认为生产分支) |
GET /sites/{site_id}/database/branch/{deploy_id} | 获取分支连接字符串(不存在时返回 404) |
DELETE /sites/{site_id}/database/branch/{deploy_id} | 删除某次部署的分支 |
POST /sites/{site_id}/database/snapshot | 创建分支快照(默认为生产分支) |
GET /sites/{site_id}/database/snapshots | 列出快照 |
DELETE /sites/{site_id}/database/snapshot/{snapshot_id} | 删除快照 |
POST /sites/{site_id}/database/snapshot/{snapshot_id}/restore | 将快照还原到某个分支(默认为生产分支) |
删除分支和还原快照具有破坏性,必须先征得用户明确确认。 快照还原不应作为常规的生产回滚手段。
测试
用于单元/集成测试的纯 Postgres(不含函数):
import { NetlifyDB } from "@netlify/database-dev"; // npm i -D @netlify/database-dev
import { Client } from "pg";
import { afterAll, beforeAll, expect, test } from "vitest";
let db: NetlifyDB, connectionString: string;
beforeAll(async () => {
db = new NetlifyDB();
connectionString = await db.start();
await db.applyMigrations("./netlify/database/migrations");
});
afterAll(async () => { await db.stop(); });
test("inserts and reads a user", async () => {
const client = new Client({ connectionString });
await client.connect();
await client.query("INSERT INTO users (name) VALUES ($1)", ["Ada"]);
const { rows } = await client.query("SELECT name FROM users");
expect(rows).toEqual([{ name: "Ada" }]);
await client.end();
});
NetlifyDB(options?):directory(持久化到磁盘;省略则使用内存)、port(默认为随机端口)、logger。
完整的 Netlify 环境(函数/边缘函数会像生产环境一样读取 NETLIFY_DB_URL):
import { NetlifyDev } from "@netlify/dev"; // npm i -D @netlify/dev
const netlifyDev = new NetlifyDev({ projectRoot: "./fixtures/my-project" });
await netlifyDev.start(); // sets NETLIFY_DB_URL in the runtime
// ...tests...
await netlifyDev.stop();
数据库分支(部署时)
只有生产部署会访问生产数据库。每个部署预览都会获得自己的分支,并在创建预览时复制一份生产数据;在该分支中进行的架构/数据更改不会影响生产环境。自动接入,无需更改代码。
预览分支可能包含生产数据,包括 PII;而且部署预览链接是公开的。分享预览链接前,请提醒用户。
运行时注意事项
Environment not configured(getDatabase()无法解析连接字符串):在 Netlify 之外运行、使用 Lambda 兼容模式的 Functions,或使用过时的 CLI。解决方法:显式传入connectionString。
Lambda 兼容模式是唯一必须自行传入const db = getDatabase({ connectionString: "postgres://..." });connectionString的原始环境。database feature not available for this account— 需要采用基于 Credit 的套餐。compute customization requires a Pro or higher plan— 自动扩缩/休眠设置需要 Pro 或更高套餐;Free/Personal 使用默认设置。branch limit reached: maximum <N> branches...— 每个活动的部署预览都会占用一个分支;删除不需要的分支或升级套餐。database not found— 尚未配置数据库;运行netlify database init。cannot reset the production branch— 只能重置非生产分支。
限制
- 套餐: Netlify Database 仅适用于基于 Credit 的套餐;活动数据库会消耗计算和带宽额度。存储免费至 2026 年 7 月 1 日。
- 权限: 只有 Team Owner 可以删除数据库;只有 Team Owner 和 Developer 可以查看连接字符串(
Access Denied表示角色权限不足)。 - 密钥: 连接字符串包含用户名和密码。切勿将其提交到代码库;请存储在密钥管理器/环境变量提供方中。
将现有 Postgres 项目切换到 Netlify Database
分为三个阶段:配置(在分支上创建基准架构)、演练(替换代码,将数据复制到预览分支并验证)、切换(将数据导入生产环境并合并)。适用于任何 Postgres 来源(Neon、Supabase、RDS、自行管理的实例、旧版 @netlify/neon)。使用 pg_dump/pg_restore(版本需与来源匹配)。切换期间会有短暂的数据丢失窗口——最终导出后、生产部署前写入来源的数据不会同步过来。
阶段 2/3 代码替换(Drizzle):
import { drizzle } from "drizzle-orm/netlify-db";
import * as schema from "./schema";
export const db = drizzle({ schema });
完整的分步说明(转储标志、回滚、清理):references/migration-from-extension.md 和 references/legacy-extension.md。
Netlify 规范(数据库)
这些是组织约定,并非文档事实——它们由 ctx-gen 合并到渲染后的技能中,且不会自动生成。由技能维护者负责。
- 生产数据变更必须通过 DML 迁移表达——Agent 绝不能直接编辑行(虽然人类可以在 UI 中编辑行,但这不是面向 Agent 的操作界面)。
- 预览分支可能包含生产数据,包括 PII,且预览部署链接是公开的。分享前请发出警告。
- 只能使用有文档说明的操作界面:不得通过内部端点运行原始 psql,不得抓取
netlify api,也不得读取本地 CLI 配置文件中的令牌。 - 深入指南位于此技能中:
references/operational-footguns.md、references/migrations.md、references/local-dev.md、references/cli-commands.md、references/migration-from-extension.md、references/legacy-extension.md。 - 架构变更只能通过已提交的迁移文件,由部署应用到托管数据库。绝不能以任何形式对 Netlify 托管的数据库运行
drizzle-kit push,绝不能对NETLIFY_DB_URL运行drizzle-kit migrate,也绝不能通过netlify database connect或任何直接连接应用 DDL。 - 如果
netlify命令或部署失败,向用户展示确切的错误、部署日志 URL 以及受影响的站点/分支,然后停止——不要编造恢复命令,也不要升级到更底层的工具。 - 首次部署时,
createSiteDatabase出现401 Access Denied:如果发生在先执行--prod的部署中,则改为先部署预览(netlify deploy,不带--prod)并重试;如果预览也失败,则报告并停止。绝不能通过 curlapi.netlify.com、运行netlify api createSiteDatabase或从本地 CLI 配置中提取令牌来绕过问题。 - 更改现有数据的请求可能指生产环境,也可能指预览分支——如果提示词未说明,请先询问。代替他人操作时,默认不触碰生产环境。
- 破坏性数据库操作——REST 分支删除、快照恢复以及任何重置——都必须先获得用户明确确认。正文不得将快照恢复描述为常规的生产回滚手段。
- 固定版本:
drizzle-orm和drizzle-kit必须从@beta安装——latest缺少drizzle-orm/netlify-db适配器,会导致失败。正文不得将此要求弱化为建议。