为什么大家都在Next.js里用Prisma、Zod和tRPC?
Next.js 是一个用于构建全栈 web 应用程序的 React 框架。你可以用 React 组件来打造用户界面,并使用 Next.js 进行额外的功能和优化。
该框架已经成熟,成为许多希望构建高性能全栈 web 应用程序的前端开发者的得力工具,但也由于 Server Components 使它显得有些复杂,让开发者回想起后端世界。本文不会深入了解 Next.js 的哲学以及其内部工作方式,但如果你对此感到好奇,或者想要从这里开始,我非常推荐观看 T3 Stack 的创造者 Theo 制作的视频:Next.js 真正的工作方式
Zod + PrismaZod 架构模式和 Prisma 模型定义在 TypeScript 应用程序的不同部分中有着不同的用途,尽管它们可以很好地相互补充。以下是每个的简要概述及其不同之处:
佐德架构
Zod 是一个优先使用 TypeScript 的模式定义和验证库。Zod 模式定义主要用于运行时的验证和类型推断。
主要特点:
- 验证:Zod 允许你定义可以在运行时用于验证数据的模式。这特别有助于确保从外部来源(如 API 或用户输入)接收到的数据的完整性。
- 类型推断:Zod 可以自动从模式推断 TypeScript 类型,确保代码中的类型安全。
- 自定义消息:Zod 模式可以包含验证失败时的自定义消息。
- 转换:Zod 支持在验证数据的同时进行数据转换。
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
age: z.number().int().nonnegative().int(),
});
// 用这个模式来验证数据
const user = UserSchema.parse({
id: 1,
name: "John Doe",
email: "john.doe@example.com",
age: 30,
});
Prisma模型
Prisma 是一个 ORM(对象关系映射器)工具,允许你定义数据库架构并使用 TypeScript 来操作数据库。Prisma 模型定义用于定义数据库表结构。
主要特点:
- Schema 定义:Prisma 模型定义了数据库表的形状,包括字段、类型以及表之间的关系。
- 迁移:Prisma 可以根据模型的更改生成并运行迁移,确保数据库模式与应用程序同步。
- 数据库操作:Prisma 提供了一个强大的 TypeScript API 用于查询和操作数据库中的信息。
- 类型安全:Prisma 根据您的模型生成 TypeScript 类型,确保数据库操作过程中类型的安全性。
// schema.prisma
model User {
id Int @id @default(autoincrement()) // 自动递增的ID
name String
email String @unique
age Int
}
// 生成的 TypeScript 类型和客户端代码
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.create({
data: {
name: "John Doe",
email: "john.doe@example.com",
age: 30,
},
});
console.log(user); // 打印用户信息
}
main();
重要差异
使用场景:
- Zod : 用于在 TypeScript 应用程序中进行运行时验证和类型推导。它确保应用处理的数据符合预期格式和限制。
- Prisma : 用于定义和管理数据库模式定义并与数据库进行交互。它提供了一个 ORM,以类型安全的方式执行数据库操作,使操作更加安全可靠。
重点:
- Zod : 专注于在你的应用程序中验证并转换数据。
- Prisma :专注于将你的 TypeScript 模型映射到数据库表,并管理数据模式的结构。
整合
- Zod : 可以在应用程序中任何需要数据验证的场景下使用,包括请求的验证、表单验证等等。
- Prisma : 主要用于应用程序的数据访问层,处理与数据库的交互。
类型生成:
- Zod : 生成基于验证规则的 TypeScript 类型。
- Prisma : 生成基于数据库结构的 TypeScript 类型。
Zod 和 Prisma 可以在 TypeScript 应用程序中有效结合使用,共同确保数据流的准确性。你可以选择使用 Prisma 定义数据库结构并执行数据库操作,同时使用 Zod 在应用程序的边界,例如在将 API 请求负载保存到数据库之前进行验证,以确保流入数据库的数据始终有效且符合预期格式。这确保流入数据库的数据始终有效并符合预期格式。
例如,您可以定义一个 Zod 模式来匹配一个 Prisma 模型,从而在生成新的数据库记录之前验证输入,以便确保数据的准确性。
import { z } from 'zod';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const 用户模式 = z.object({
名称: z.string(),
邮箱: z.string().email(),
年龄: z.number().int().nonnegative(),
});
async function 创建用户(data: unknown) {
const 验证数据 = 用户模式.parse(data);
const 用户 = await prisma.user.create({
data: 验证数据,
});
return 用户;
}
就这样,Zod 和 Prisma 一起,帮助维护应用逻辑和数据库的数据完整。
注意 : 大多数情况下,你的验证模式(schema)与 Prisma 列并不相同。这是因为你的数据库会有表之间的关系和规范化,而在请求中只是一个大的 JSON。
tRPC 在这里是怎么回事?快速行动,但别搞砸。你刚在tRPC文档首页看到的就是这句话。
tRPC(TypeScript 远程过程调用)是一个库,允许你在 TypeScript 应用中构建端到端的类型安全 API。结合 Zod 和 Prisma,tRPC 可以创建一个强大且类型安全的 API 层,无缝集成数据验证和数据库操作。以下是 tRPC 如何融入其中的说明:
tRPC 简介
tRPC旨在简化在TypeScript中创建和使用类型安全的API的过程。它省去了定义明确的API路由的需要,而是允许您直接从客户端调用服务器端的函数。
主要特点:
- 端到端类型安全:确保服务器和客户端之间共享类型,从而防止类型不匹配。
- 避免模式重复:使用TypeScript的类型推断来减少冗余的类型定义。
- 可以与Zod和Prisma一起使用:可以利用Zod进行输入验证和Prisma进行数据库操作,确保整个应用的数据一致性和完整性。
带有验证的程序定义:
使用 Zod 架构来验证您的 tRPC 过程的输入数据。这确保了服务器接收到的数据在处理前符合预期的结构和限制。
数据库相关操作:
使用 Prisma 在你的 tRPC 过程中与数据库进行交互。这确保了数据操作是类型安全的,并且与你的数据库模式保持一致。
类型安全的API接口层
tRPC 提供了一种类型安全的接口,可以从客户端调用服务器端的过程。这确保了在编译时发送到或从服务器接收到的任何数据都会经过类型检查。
示例整合我们来看一个简单的例子,把所有内容串起来。
棱镜风格在 schema.prisma
文件中创建一个简单的 Prisma 数据模型
用户模型 User {
id Int @标识符 @default(autoincrement())
name String
email String @唯一
age Int
}
佐德架构
定义一个用于用户输入验证的 Zod 模式:
import { z } from 'zod';
const UserSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().int().nonnegative(),
});
tRPC 路由
定义一个使用 Zod 架构和 Prisma 客户端的 tRPC 路由器:
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import { PrismaClient } from '@prisma/client';
const t = initTRPC.create();
const prisma = new PrismaClient();
const 用户路由 = t.router({
创建用户: t.procedure
.input(用户模式)
.mutation(async ({ input }) => {
const 用户 = await prisma.user.create({
data: input,
});
return 用户;
}),
获取用户: t.procedure
.input(z.object({ id: z.number() }))
.query(async ({ input }) => {
const 用户 = await prisma.user.findUnique({
where: { id: input.id },
});
return 用户;
}),
});
export type AppRouter = typeof 用户路由;
tRPC (t远程过程调用) 服务端
注:tRPC 是一种技术术语,通常保持英文原样或解释为缩写。
配置 tRPC 服务器:
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { userRouter } from './userRouter';
const server = createHTTPServer({
router: userRouter,
createContext: () => ({}),
});
server.listen(4000, () => {
console.log('服务器已经在 http://localhost:4000 启动');
});
tRPC 客户端
如何从 tRPC 客户端调用 API:
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './userRouter';
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:4000',
}),
],
});
async function main() {
const newUser = await trpc.createUser.mutate({
name: 'John Doe',
email: 'john.doe@example.com',
age: 30,
});
console.log('新创建的用户为:', newUser);
const fetchedUser = await trpc.getUser.query({ id: newUser.id });
console.log('刚获取的用户为:', fetchedUser);
}
main();
tRPC :提供一个类型安全的 API 接口层,使得客户端和服务器可以在不手动定义路由的情况下进行通信。
Zod : 进行 tRPC 过程输入数据的验证,确保在处理前数据的完整性。
Prisma :处理 tRPC 过程中的数据库事务,确保类型安全并保持与数据库模式的一致性。
通过使用 tRPC 与 Zod 和 Prisma,你可以从客户端到服务器,再到数据库创建一个无缝且类型安全的工作流,减少运行时错误的风险并提高开发效率,简化开发流程。
不用 tRPC?没有 tRPC,你得手动为 getUser
和 createUser
定义路由,使用如 Express 这样的 web 框架。这里有个用 Express 设定这些路由的例子,比如用 Zod 来验证输入,用 Prisma 来操作数据库。
棱镜模型
确保你已经在 schema.prisma
中定义了 Prisma 模式。
model User {
id Int @id @default(autoincrement()) // 用户ID,自动递增
name String // 用户姓名
email String @unique // 用户电子邮件,唯一
age Int // 用户年龄
}
佐德模式
为输入验证定义 Zod 的架构以进行输入验证。
import { z } from 'zod';
const CreateUserSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().int().min(0),
});
const GetUserSchema = z.object({
id: z.number(),
});
Express
搭建一个具有 createUser
和 getUser
接口的 Express 服务器:
import express from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
const prisma = new PrismaClient();
const app = express();
app.use(express.json());
app.post('/createUser', async (req, res) => {
try {
const parsedData = CreateUserSchema.parse(req.body);
const user = await prisma.user.create({
data: parsedData,
});
res.status(201).json(user);
} catch (e) {
if (e instanceof z.ZodError) {
return res.status(400).json(e.errors);
}
res.status(500).json({ message: 'Internal server error' });
}
});
app.get('/getUser', async (req, res) => {
try {
const parsedQuery = GetUserSchema.parse(req.query);
const user = await prisma.user.findUnique({
where: { id: parsedQuery.id },
});
if (!user) {
return res.status(404).json({ message: '找不到用户' });
}
res.json(user);
} catch (e) {
if (e instanceof z.ZodError) {
return res.status(400).json(e.errors);
}
res.status(500).json({ message: 'Internal server error' });
}
});
app.listen(4000, () => {
console.log('服务器运行在 http://localhost:4000');
});
解释如下
快速安装:
创建一个 Express 实例,并使用 express.json()
配置它来解析 JSON 请求体。
创建用户路由:
- 验证:使用 Zod 的
parse
方法使用CreateUserSchema
验证请求体。 - 数据库操作:使用 Prisma 在数据库中根据验证后的数据新建一个用户。
- 错误处理:验证失败时返回 400 状态码,并附带验证错误;若出现服务器错误,则返回 500 状态码。
获取用户路线信息:
- 验证:使用 Zod 的
parse
方法对请求查询进行验证,将请求查询与GetUserSchema
进行对比。 - 数据库操作:使用 Prisma 根据 ID 在数据库中查找用户信息。
- 错误处理:如果验证失败,返回 400 错误并附带验证错误详情。如果未找到用户,则返回 404 错误。如果出现服务器错误,则返回 500 错误。
这种方法允许你手动处理路由,同时仍然可以享受Zod和Prisma提供的类型安全和验证的好处。然而,相比使用tRPC,你需要编写更多的样板代码,因为tRPC抽象了很多这种手动操作。
更多阅读这里有很多细节我没提到,我自己也在不断学习中,但我强烈推荐以下几篇文章:
共同学习,写下你的评论
评论加载中...
作者其他优质文章