使用NestJS和Prisma构建安全的RESTful API ... - ZenStack

作者:API传播员 · 2026-01-17 · 阅读时间:6分钟
本文介绍使用NestJS和Prisma构建安全RESTful API的三种方法,包括传统手动权限检查、ZModel声明式规则定义和ZenStack增强API,旨在减少代码量并提升开发效率,涵盖用户角色管理和帖子访问控制等安全需求。

使用 NestJS 和 Prisma 构建安全的 RESTful API

NestJS 是一个功能强大且文档完善的框架,适用于构建各种后端应用程序,包括 RESTful、GraphQLWebSocket 和微服务等。在后端开发中,基于数据库构建 API 是开发者的核心任务之一。而随着 Prisma ORM 的流行,越来越多的开发者选择将其与 NestJS 结合使用,以实现更高效的开发体验。

本文将介绍三种构建安全 RESTful API 的方法,逐步展示如何减少代码量并提升开发效率


初始项目配置

在我们的示例项目中,已经配置了一个用于数据库访问的 PrismaService,并实现了一些基本的 CRUD 端点。这些端点可以列出用户和帖子,并管理帖子的发布状态。然而,这些 API 缺乏访问控制,任何人都可以随意操作数据。

为了让 API 更加安全和实用,我们需要满足以下业务需求:

用户相关规则

  1. 所有人可以创建账户。
  2. 除了“电子邮件”字段,所有用户都可以查看其他用户的配置文件。
  3. 用户只能更新自己的个人资料。
  4. 用户分为两种角色:“用户(USER)”和“编辑(EDITOR)”。

帖子相关规则

  1. “用户”角色的用户可以完全操作自己的帖子,但不能更新 published 字段;对其他用户的帖子没有写权限。
  2. “编辑”角色的用户可以完全操作所有帖子,包括更新 published 字段。
  3. 已发布的帖子对所有人可读。

为了简化示例,我们将使用 x-user-idx-user-role HTTP 标头来模拟用户身份和角色,而非实现完整的身份验证模块。


方法一:传统实现方式

最直接的实现方式是通过代码手动检查权限。例如,我们可以通过 AsyncLocalStorage 提取用户身份,并在控制器和服务中注入身份信息。以下是一些示例代码:

// 示例代码:检查用户权限
if (user.role === 'USER' && user.id !== post.authorId) {
    throw new Error('Unauthorized');
}

尽管这种方式可以满足需求,但手动编写复杂的条件逻辑容易出错,且难以维护。


方法二:使用 ZModel 声明式定义规则

为了简化权限管理,我们可以使用 ZenStack 的 ZModel 以声明式方式定义访问规则。以下是示例规则:

model User {
    email String @auth((user) => user.id === this.id) // 仅用户自己可见

    profile String @auth((user) => user.id === this.id || user.role === 'EDITOR')}model Post {
    published Boolean @auth((user) => user.role === 'EDITOR')    authorId Int @auth((user) => user.id === this.authorId || user.role === 'EDITOR')}

规则说明

  1. 用户只能查看自己的电子邮件地址。
  2. 用户可以完全访问自己的个人资料。
  3. 其他用户可以查看所有配置文件,但不能查看电子邮件字段。
  4. 只有“编辑”角色的用户可以更新 published 字段。
  5. 作者和“编辑”角色用户可以完全访问帖子。
  6. 已发布的帖子对所有人可见。

通过运行 npx zenstack-generate,我们可以生成 Prisma 模式和相关支持代码。


方法三:使用 Enhanced Prisma Service

ZenStack 提供了一个增强的 Prisma API,可以自动注入访问控制逻辑。我们可以创建一个 EnhancedPrismaService,并将其作为现有 PrismaService 的包装器:

import { EnhancedPrismaClient } from '@zenstackhq/runtime';

@Injectable()
export class EnhancedPrismaService extends PrismaService {
    constructor() {
        super();
        this.client = new EnhancedPrismaClient(this.client);
    }
}

工作原理

  • EnhancedPrismaClient 会拦截 CRUD 调用,并根据 ZModel 中的规则自动执行权限检查。
  • 例如,当调用 enhancedPrisma.post.findMany() 时,返回的结果会自动过滤为当前用户可访问的帖子。

通过这种方式,我们可以从控制器中移除冗余的权限检查逻辑,代码更加清晰简洁。


方法四:自动生成 RESTful API

ZenStack 提供了内置的 ExpressJS 中间件,可以基于 ZModel 自动生成完整的 RESTful CRUD API。由于 NestJS 默认使用 Express 处理 HTTP 请求,我们可以直接集成该中间件。

实现步骤

  1. 创建一个封装 ZenStack 中间件的 NestJS 中间件:

    import { ZenStackMiddleware } from '@zenstackhq/express';
    
    @Injectable()
    export class ZenStackNestMiddleware {
        use(req: Request, res: Response, next: NextFunction) {
            ZenStackMiddleware(req, res, next);
        }
    }
  2. 在路由中安装中间件:

    app.use('/api', zenStackNestMiddleware);
  3. 启用 OpenAPI 支持:
    在 ZModel 文件中启用 @zenstackhq/[openapi](https://www.explinks.com/blog/wx-safe-and-user-friendly-openapi) 插件,并运行 npx zenstack-generate 生成 OpenAPI 文档。


总结

通过本文介绍的三种方法,我们可以逐步优化 NestJS 和 Prisma 的 API 开发流程:

  1. 传统实现方式适合简单场景,但代码复杂且难以维护。
  2. 使用 ZModel 声明式定义规则,可以显著减少权限检查代码。
  3. 借助 ZenStack 的增强 API 和自动生成功能,可以进一步简化开发流程

最终,通过将访问控制逻辑集中到模式中,我们不仅提升了代码的可维护性,还确保了 API 的安全性

原文链接:GitHub 示例项目

编码愉快!

原文链接: https://zenstack.dev/blog/nest-api