利用 Prisma 的零成本类型安全实现高效开发

2025-06-10

利用 Prisma 的零成本类型安全实现高效开发

数据处理是 Web 应用程序的核心,当数据在应用程序代码的不同组件之间传输时,它会带来许多挑战。
在本文中,我们将探讨Prisma的零成本类型安全性,以及它如何提高生产力并增强开发人员对使用关系数据库的应用程序的信心。

Web 应用程序中的数据之旅

如果您一直在构建 Web 应用程序,那么您很可能花费了大量时间处理数据。作为开发者,您对数据的关注始于 UI,即用户输入信息或以创建信息的方式进行交互。漫长的数据旅程由此开始。旅程通常以数据库为终点;数据可能会在数据库中经历更多次旅程,包括获取、更新和再次保存。

💡 这篇文章假设您对关系数据库和全栈开发有基本的了解。

在典型的三层应用程序中,旅程如下所示:

  1. 数据通过 HTTP 从用户浏览器由前端通过 API(例如 GraphQL 或 REST API)发送到后端服务器(或无服务器功能)。
  2. 后端找到匹配的路由及其处理程序。
  3. 后端对用户进行身份验证、反序列化数据并验证数据。
  4. 路由处理程序将业务逻辑应用于数据。
  5. 数据库访问层用于将数据安全地存储在数据库中。

典型架构

数据经过的每个组件都可能操作和转换数据。在 JavaScript 中,当多个组件与数据交互时,会出现一个常见问题:类型错误

类型错误是当操作中值与代码期望的类型不同时发生的错误。

例如,连接用户对象的名字和姓氏的函数可能会遇到类型错误:

function getFullName(user) {
  return `${user.firstName} ${user.lastName}`
}
Enter fullscreen mode Exit fullscreen mode

调用函数而不传递参数会引发类型错误:

getFullName() // Uncaught TypeError: Cannot read property 'firstName' of undefined
Enter fullscreen mode Exit fullscreen mode

使用缺少属性的对象调用该函数不会引发错误:

getFullName({}) // "undefined undefined"

getFullName({ firstName: 'Shakuntala' }) // "Shakuntala undefined"
Enter fullscreen mode Exit fullscreen mode

这是因为 JavaScript 能够在运行时转换类型。在本例中,undefined被转换为string。此功能称为隐式类型强制转换

对于 JavaScript,这些错误发生在运行时。实际上,这意味着这些错误是在测试期间或应用程序部署后发现的。

使用 TypeScript 进行类型安全

近年来,TypeScript 作为一种可编译为 JavaScript 的类型化语言,在 JavaScript 开发者中广受欢迎。TypeScript 的主要优势之一是能够在编译时检测类型错误,这增强了你对所构建应用程序的信心。

例如,我们可以getFullName按如下方式定义上面的函数:

function getFullName (user: {firstName: string, lastName: number}) => (
  return `${user.firstName} ${user.lastName}`
)

getFullName({}) // Type error
Enter fullscreen mode Exit fullscreen mode

由于函数定义下面的调用无效,因此运行 TypeScript 编译器时将捕获该错误:

$ tsc example.ts

example.ts:5:13 - error TS2345: Argument of type '{}' is not assignable to parameter of type '{ firstName: string; lastName: number; }'.
  Type '{}' is missing the following properties from type '{ firstName: string; lastName: number; }': firstName, lastName

5 getFullName({})
Enter fullscreen mode Exit fullscreen mode

除了 TypeScript 的优点之外,与 JavaScript 相比,TypeScript 需要以定义类型为代价,这通常会降低生产力。

更改数据和类型错误

类型错误在快速开发和原型设计过程中尤其常见,因为引入新功能需要改变数据结构。

Users例如,博客可能有和的概念Posts,因此author可以有多个posts。通常,这两个实体中的每一个都具有如下图所示的结构:

数据库架构

如果您决定重命名该name字段firstName并添加新lastName字段,则需要更新数据库架构。但是,一旦数据库架构迁移(更新为新结构),后端可能会失败,因为其查询仍然指向name不存在的字段。

改变数据库架构

这种变化称为模式迁移,有很多方法可以处理此类变化。例如,最简单的方法可能如下所示:

您安排一个维护时段并利用之前的时间来:

  1. 更新后端代码以使用新字段。
  2. 在测试环境中迁移数据库模式。
  3. 使用迁移的数据库模式测试更新的后端。
  4. 如果测试成功,请使用维护窗口关闭旧版本的后端,迁移数据库模式,然后部署更新的后端。

这种方法的问题之一(除了必须关闭服务之外)是,更新代码以使用新字段是一个手动过程。由于访问旧name字段的代码在语法上仍然有效,因此代码运行时会发生类型错误。具体来说,不会抛出任何错误,因为访问未定义的字段不会像上例TypeError中那样抛出错误getFullName

可以通过几种方式将代码适应新的模式,这些方式可以组合使用:

  • 手动搜索代码中所有出现的name并进行调整以适应架构更改。
  • 使用单元测试和集成测试。您可以先创建新的测试来描述变更后的预期行为。测试最初可能会失败,但随着代码更新,随着代码适应新字段的使用,测试会逐渐通过。

根据您访问数据库的方式,这两种方法都可能很繁琐。使用像knex.js这样的 SQL 查询构建器,您必须使用旧字段搜索查询name并更新它们。使用 ORM,您通常必须更新User模型并确保该模型不用于访问或操作旧name字段。

在使用 knex.js 的应用程序中,更改如下所示:

const user = await db('users')
-  .select('userId', 'name', 'twitter', 'email)
+  .select('userId', 'firstName', 'lastName', 'twitter', 'email)
  .where({
    userId: requestedUserId
  })

await db('users')
  .where({ userId: userIdToUpdate })
-  .update({ name: newName })
+  .update({ firstName: newFirstName, lastName: newLastName })
Enter fullscreen mode Exit fullscreen mode

这里的挑战是,无论具体的数据库抽象如何,您都需要协调数据库和代码库之间的变化。

Prisma 方法简化了代码库和数据库模式之间的协调工作。

Prisma – 现代数据库工具包

Prisma 2是一个开源数据库工具包,其构建时就考虑到了类型安全的优势。

在本文中,我们将介绍 Prisma Client,它是该工具包针对 Node.js 和 TypeScript 的类型安全数据库客户端。

💡 Prisma 的迁移工具 Prisma Migrate 目前处于实验阶段。更多信息,请参阅Prisma Migrate 文档

Prisma 与数据库无关,支持不同的数据库,包括 PostgreSQL、MySQL 和 SQLite。

生成的 Prisma 客户端采用 TypeScript 编写,这使得类型安全成为可能。**好消息是,您可以在用 JavaScript 编写的 Node.js 应用程序中获得类型安全的一些好处,而无需花时间定义数据库层的类型。

此外,Prisma 可以作为更深入地了解 TypeScript 优势的门户。

以模式为中心的工作流程

Prisma 使用Prisma 模式作为数据库的声明式和类型化模式。它作为数据库和客户端的可信来源,客户端的可信来源由 Prisma 模式自动生成。Prisma 模式只是数据库的另一种表示形式。对于上面的示例,相应的 Prisma 模式如下所示:

model User {
  id      Int     @default(autoincrement()) @id
  email   String  @unique
  name    String?
  twitter String?
  posts   Post[]
}

model Post {
  postId   Int     @default(autoincrement()) @id
  title    String
  content  String?
  author   User?   @relation(fields: [authorId], references: [id])
  authorId Int?
}
Enter fullscreen mode Exit fullscreen mode

Prisma 支持不同的工作流程,具体取决于您是从头开始还是使用现有数据库。

假设您已经定义了数据库模式(使用 SQL 或迁移工具),Prisma 的工作流程从高层次来看如下所示:

  1. 您可以使用创建 Prisma 模式的 Prisma CLI 来检查数据库。
  2. 您可以使用 CLI 生成 Prisma 客户端(它使用 Prisma 模式作为数据库模式的表示)。您将获得一个根据您的数据库模式定制的节点模块。

自省工作流程

通过数据库自检和 Prisma Client 生成,您现在可以按如下方式使用 Prisma Client:

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// An example route handler for POST /api/user
// Required fields in body: name, email
export default async function handle(req, res) {
  const { name, email } = req.body
  const createdUser = await prisma.user.create({
    data: {
      name,
      email,
    },
  })

  res.json({
    id: createdUser.id,
    name: createdUser.name,
    email: createdUser.email,
  })
}
Enter fullscreen mode Exit fullscreen mode

生成的 Prisma 客户端(从 导入)的吸引力@prisma/client在于,所有prisma方法的输入参数和返回值都是完全类型化的。例如,在 VSCode 中,您可以右键单击createdUserGo to Type Definition这将导致生成的 TypeScript 代码:

export type User = {
  id: number
  email: string
  name: string | null
  twitter: string | null
}
Enter fullscreen mode Exit fullscreen mode

由于这些类型,代码编辑器和 CLI 工具等工具可以在后台进行大量检查,并在您编写代码时提供可操作的反馈。例如,尝试访问不存在的字段,createdUser.address就会被快速检测到并收到通知。

通过更好地理解数据库模式、Prisma 模式和生成的 Prisma 客户端之间的关系,让我们看看通过在后台使用生成的类型使用 JavaScript 提供这种可操作反馈的工具。


零成本安全,高效安全发展

在使用 Prisma 的 JavaScript 项目中,您可以零成本获得类型安全的优势。这意味着您无需付出任何额外努力就能对自己的代码更加自信。

它有几个层次。

级别 1:自动完成建议

零成本类型安全的第一个示例是键入时弹出 VSCode IntelliSense 建议的方式:

自动完成功能正在运行

自动完成功能

生成的@prisma/clientCRUD API 是根据你的数据库架构定制的,并且完全使用 TypeScript 类型。这使得 VSCode 的 IntelliSense 能够在开发过程中提供类型化的自动完成建议。


第 2 级:VSCode 中的类型安全验证

建议是一项很棒的功能,它可以提高工作效率,并减少在阅读文档和编写代码之间的时间浪费。当你的代码以非预期的方式使用 Prisma API 时,可能会出现错误(就像 VSCode 中的 linters 一样),从而违反类型规范。

添加// @ts-check到使用 Prisma 客户端的 JavaScript 文件的顶部。VSCode 将通过 TypeScript 编译器运行你的代码并报告错误:

使用@ts-check,输入参数原始`prisma.user.create()` endraw 和返回值原始`createdUser` endraw 的类型与 DB 字段类型匹配

使用@ts-check检查输入参数“prisma.user.create()”和返回值“createdUser”的类型是否与数据库字段类型匹配

select如果在调用中缩小返回的字段,则prisma.user.create()返回的内容createdUser将相应地输入:

访问未选定字段时出现选择集和类型错误

访问未选定字段时出现选择集和类型错误

为了使其工作,请在 VSCode 中启用语法检查:

在您的 VSCode 配置中设置javascript.validate.enabletrue

{
  "javascript.validate.enable": true
}
Enter fullscreen mode Exit fullscreen mode

虽然这在开发过程中提供了宝贵的反馈,但没有什么能阻止你提交或部署有错误的代码。这时,自动类型检查就派上用场了。


第 3 级:CI 中的自动类型检查

与 VSCode 运行 TypeScript 编译器进行类型检查的方式类似,您可以在 CI 中或作为提交钩子运行类型检查。

  1. 将 TypeScript 编译器添加为开发依赖项:
npm install typescript --save-dev
Enter fullscreen mode Exit fullscreen mode
  1. 运行 TypeScript 编译器:
npx tsc --noEmit --allowJs --checkJs pages/api/*.js
Enter fullscreen mode Exit fullscreen mode

要作为提交钩子运行:

Husky允许你在你的package.json

您可以安装 Husky:

npm install husky --save-dev
Enter fullscreen mode Exit fullscreen mode

并添加钩子:

{
  // package.json
  "husky": {
    "hooks": {
      "pre-commit": "tsc --noEmit --allowJs --checkJs pages/api/*.js"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

结论

类型错误是 JavaScript 中常见的问题,由于它们在运行时才会被发现,因此如果没有经过严格的测试,检测起来会非常困难。当处理跨多个组件和数据库的数据时,与此类类型错误相关的风险会更大。

TypeScript 的类型安全性减轻了其中的一些风险,但需要学习 TypeScript 并预先定义类型。

在为了适应新功能而快速变化的应用程序中,数据库模式必须通过模式迁移进行调整,进而调整应用程序代码。

必须手动管理此类迁移可能会容易出错且很麻烦,从而降低了快速迭代应用程序而不引入错误的能力。

Prisma 通过以模式为中心的工作流自动生成的 TypeScript 数据库客户端解决了这些挑战。这些功能在构建期间具有自动完成和自动类型检查功能,从而提高了生产力并增强了开发人员的信心,从而带来了愉悦的开发人员体验。

这些好处是零成本的,因为作为开发者,您无需采取任何额外的预防措施或步骤即可使用 Prisma 获得类型安全的优势。最重要的是,所有这些都可以在纯 JavaScript 编写的项目中实现。

鏂囩珷鏉ユ簮锛�https://dev.to/prisma/productive-development-with-prisma-s-zero-cost-type-safety-4od2
PREV
Twitter 的 12 个 CSS 最佳实践
NEXT
使用 Netdata 监控你的 LEMP 服务器