Next.js 16 入门教程
数据库集成
本教程共 42 篇 · 第 26 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
Next.jsNext.js 16 入门教程数据库PrismaDrizzleServer Components
26. 数据库集成
本节目标:理解 Next.js 中数据库集成的核心模式,掌握 Server Components 直连数据库的方式,了解 Prisma 和 Drizzle 两大 ORM 的特点与使用,以及连接池的最佳实践。
Server Components 直连数据库
App Router 有个好处:Server Components 可以直接访问数据库,不用再走 API 层中转。这样代码少了,也不会暴露内部数据源。
// app/posts/page.tsx
import { db } from '@/lib/db'
export default async function PostsPage() {
// 直接在 Server Component 中查询数据库
const posts = await db.query.posts.findMany({
orderBy: desc(posts.createdAt),
limit: 10,
})
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
Important在 Server Components 中直接 fetch 数据时,不要使用 Route Handlers 中转。因为 Server Components 在服务器上执行,额外的 HTTP 请求只会增加延迟。
何时使用 Route Handlers
虽然 Server Components 可以直接访问数据库,但以下场景仍需 Route Handlers:
- 客户端组件需要数据:Client Components 无法直接访问数据库,需要通过 Route Handlers 获取数据
- 频繁轮询的数据:客户端需要实时更新的数据
- 依赖客户端 Web API 的数据:如地理位置、文件 API 等
// app/api/posts/route.ts
import { db } from '@/lib/db'
export async function GET() {
const posts = await db.query.posts.findMany({
orderBy: desc(posts.createdAt),
limit: 10,
})
return Response.json(posts)
}
Prisma ORM
Prisma 是目前最流行的 Node.js ORM 之一,提供类型安全的查询 API 和自动迁移功能。
安装与初始化
npm install prisma @prisma/client
npx prisma init
定义数据模型
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
生成客户端并查询
npx prisma generate
npx prisma db push
// lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const db = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
// app/posts/page.tsx
import { db } from '@/lib/db'
export default async function PostsPage() {
const posts = await db.post.findMany({
include: { author: true },
orderBy: { createdAt: 'desc' },
})
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>作者: {post.author?.name}</p>
</article>
))}
</div>
)
}
Drizzle ORM
Drizzle 是一个轻量级、类型安全的 ORM,更接近 SQL 语法,性能优秀。
安装与配置
npm install drizzle-orm pg
npm install -D drizzle-kit
定义 Schema
// db/schema.ts
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow().defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
})
创建数据库连接
// db/index.ts
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,
})
export const db = drizzle(pool, { schema })
查询数据
// app/posts/page.tsx
import { db } from '@/db'
import { posts, users } from '@/db/schema'
import { desc, eq } from 'drizzle-orm'
export default async function PostsPage() {
const allPosts = await db
.select({
id: posts.id,
title: posts.title,
authorName: users.name,
})
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
.orderBy(desc(posts.createdAt))
return (
<div>
{allPosts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>作者: {post.authorName}</p>
</article>
))}
</div>
)
}
Prisma vs Drizzle 对比
| 特性 | Prisma | Drizzle |
|---|---|---|
| 类型安全 | 自动生成类型 | 推断类型,无需生成 |
| 查询风格 | 链式 API | 接近 SQL 的查询构建器 |
| 迁移工具 | 内置迁移系统 | Drizzle Kit 迁移 |
| 性能 | 良好 | 更优(轻量级) |
| 学习曲线 | 中等 | 较低(熟悉 SQL 的话) |
| 包体积 | 较大 | 较小 |
| 关系查询 | 直观 | 需要手动 join |
连接池配置
在 Serverless 环境(如 Vercel)中,数据库连接池非常重要。因为每个 Serverless 函数实例都会创建新的数据库连接,容易导致连接耗尽。
使用 Vercel Postgres 连接池
// lib/db.ts
import { db } from '@vercel/postgres'
export async function getData() {
const client = await db.connect()
try {
const data = await client.sql`SELECT * FROM posts`
return data.rows
} finally {
client.release()
}
}
Prisma 连接池配置
// prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL") // 用于迁移
}
// lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const db = globalForPrisma.prisma ?? new PrismaClient({
datasourceUrl: process.env.DATABASE_URL,
})
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
Drizzle 连接池配置
// db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres'
import { Pool } from 'pg'
// 全局缓存连接池
const globalForDb = globalThis as unknown as {
pool: Pool | undefined
}
const pool = globalForDb.pool ?? new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // 最大连接数
})
if (process.env.NODE_ENV !== 'production') globalForDb.pool = pool
export const db = drizzle(pool)
Tip在开发环境中,使用
globalThis缓存数据库连接,避免热重载时创建多个连接。
数据访问层设计
推荐创建数据访问层(DAL)来集中管理数据库操作:
// lib/dal/posts.ts
import 'server-only'
import { db } from '@/lib/db'
import { posts } from '@/db/schema'
import { desc, eq } from 'drizzle-orm'
import { cache } from 'react'
// 使用 React cache 避免重复查询
export const getPosts = cache(async () => {
return db.query.posts.findMany({
orderBy: desc(posts.createdAt),
limit: 10,
})
})
export const getPostBySlug = cache(async (slug: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.slug, slug),
})
return post
})
export const getUserPosts = cache(async (userId: number) => {
return db.query.posts.findMany({
where: eq(posts.authorId, userId),
orderBy: desc(posts.createdAt),
})
})
数据变更操作
使用 Server Action 处理数据变更:
// app/actions/posts.ts
'use server'
import { db } from '@/lib/db'
import { posts } from '@/db/schema'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
// 验证数据
if (!title || title.length < 3) {
return { error: '标题至少需要 3 个字符' }
}
// 插入数据
await db.insert(posts).values({
title,
content,
authorId: 1, // 从会话中获取
})
// 清除缓存
revalidatePath('/posts')
// 重定向
redirect('/posts')
}
小结
这一章我们学了数据库集成:
- Server Components 能直接查数据库,不用 Route Handlers 中转
- 用
globalThis缓存连接,避免开发环境连接泄漏 - Serverless 环境要注意连接池配置
- 用 DAL 集中管理数据库操作
- 数据变更走 Server Action