Bulletproof React 示例应用全景解析:数据模型、角色权限与注册流程
【免费下载链接】bulletproof-react🛡️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.项目地址: https://gitcode.com/GitHub_Trending/bu/bulletproof-react
Bulletproof React 仓库通过一个"团队 + 讨论"主题的小型示例应用,展示了生产级 React 应用所需的完整能力:认证授权、角色化访问控制、API 层抽象、Mock 数据服务等。本文以仓库官方文档 Application Overview 为核心,结合示例应用的类型定义、MSW Mock 服务与授权实现,拆解该应用的业务模型与关键实现,帮助读者理解这套架构是如何用一个简单的业务场景串联起整条技术链路的。
应用做什么:团队与讨论
官方文档对示例应用的定位非常简洁:用户可以创建团队(Team),其他用户可以加入团队,团队成员之间就不同话题展开讨论(Discussion)。这一业务看似简单,但恰好覆盖了真实应用中的几类核心问题:
- 认证与注册:登录、注册、登出、当前用户信息获取;
- 多租户隔离:所有数据按团队(teamId)隔离,用户只能访问本团队的内容;
- 角色权限:不同角色的用户拥有不同的操作能力;
- CRUD 全流程:讨论与评论的创建、编辑、删除,以及用户管理的删除操作。
仓库提供了三个可运行的示例应用,分别基于不同的技术栈,业务逻辑一致:
- React + Vite
- Next.js App Router
- Next.js Pages Router
以 Vite 版本为例,阅读 README 并按照其中的脚本说明安装依赖、启动应用与 Mock 服务器即可体验完整功能。
数据模型:四个核心实体
文档定义的数据模型包含 User、Team、Discussion、Comment 四个实体。仓库中对应的前端类型定义位于 types/api.ts,文件头部明确注释了这类 API 类型"理想情况下应自动生成并保持与后端同步"。
通用基类
所有实体都继承自BaseEntity:
export type BaseEntity = { id: string; createdAt: number; }; export type Entity<T> = { [K in keyof T]: T[K]; } & BaseEntity;分页接口则统一附带Meta元信息(page、total、totalPages),这与 Mock 层返回的分页结构一一对应。
四个实体一览
| 实体 | 关键字段 | 说明 |
|---|---|---|
User | firstName、lastName、email、role('ADMIN' \| 'USER')、teamId、bio | 用户,属于某个团队,携带角色 |
Team | name、description | 团队,有 1 个管理员和多个普通成员 |
Discussion | title、body、teamId、author | 由团队成员创建的讨论,内嵌作者信息 |
Comment | body、discussionId、author | 讨论中的消息,内嵌作者信息 |
值得注意的是Discussion与Comment直接内嵌了author: User而非仅存外键 ID——从 Mock 服务端的实现看,数据库模型中存的是authorId,返回响应时会查询用户并组装为author对象,见 discussions.ts 中findMany后按authorId回查db.user并sanitizeUser的逻辑。Mock 数据库模型的完整定义可参考 testing/mocks/db.ts,其中user模型额外持有password字段(前端类型中刻意不包含密码)。
角色与权限:ADMIN 与 USER
文档明确了两种角色及其能力边界:
| 能力 | ADMIN | USER |
|---|---|---|
| 创建/编辑/删除讨论 | ✅ | ❌ |
| 删除任意评论 | ✅ | 仅可删除自己的评论 |
| 删除用户 | ✅ | ❌ |
| 编辑自己的资料 | ✅ | ✅ |
这套规则在仓库中有两层落地:
前端授权层。lib/authorization.tsx 定义了ROLES枚举、基于角色的checkAccess钩子,以及细粒度的策略表POLICIES:
export const POLICIES = { 'comment:delete': (user: User, comment: Comment) => { if (user.role === 'ADMIN') { return true; } if (user.role === 'USER' && comment.author?.id === user.id) { return true; } return false; }, };comment:delete策略精确复现了文档中"USER 只能删自己的评论、ADMIN 可删所有评论"的规则;Authorization组件则支持按allowedRoles或自定义policyCheck布尔值渲染受保护内容,不满足时展示forbiddenFallback。
Mock 服务端(API 层)。各 Mock handler 中通过requireAuth/requireAdmin做服务端式校验,例如 users.ts 中删除用户接口会先调用requireAdmin(user),且where条件同时限定teamId,保证管理员也只能删除本团队成员;discussions.ts 中创建/编辑/删除讨论同样调用requireAdmin,查询讨论时则以teamId: { equals: user?.teamId }实现多租户隔离。这一前后台双校验的写法正是该仓库安全实践(参考 security 文档)的体现:前端隐藏操作入口只是体验问题,Mock 层代表的服务端校验才是权限的真正防线。
注册流程:团队创建的时机
文档提到一个关键业务规则:如果注册时用户没有选择加入已有团队,系统会为其创建团队,且该用户自动成为团队管理员。
这条规则的前后端实现可以完整追踪到:
- 表单层。register-form.tsx 提供一个 "Join Existing Team" 开关:关闭时提交
teamName(新团队名),开启时从 teams API 拉取的团队列表中选择teamId; - 校验层。lib/auth.tsx 中的
registerInputSchema用 Zod 定义了一个.and(...)联合校验:teamId与teamName必须二选一且互斥,从类型层面保证了文档所述的两条注册路径; - 服务端层。testing/mocks/handlers/auth.ts 的
/auth/register处理器完整实现了该规则:
if (!userObject.teamId) { const team = db.team.create({ name: userObject.teamName ?? `${userObject.firstName} Team`, }); teamId = team.id; role = 'ADMIN'; } else { const existingTeam = db.team.findFirst({ where: { id: { equals: userObject.teamId } }, }); if (!existingTeam) { return HttpResponse.json( { message: 'The team you are trying to join does not exist!' }, { status: 400 }, ); } teamId = userObject.teamId; role = 'USER'; }未传teamId则建团队并赋予ADMIN角色;传了teamId则校验团队存在性,不存在返回 400,存在则以USER角色加入。注册成功后响应通过Set-Cookie写入 JWT,同时返回AuthResponse(jwt+user),前端由react-query-auth的configureAuth(见 lib/auth.tsx)接管用户会话与ProtectedRoute路由守卫。
Mock 数据服务:没有后端也能跑通全应用
示例应用不依赖真实后端,而是通过 MSW(Mock Service Worker)模拟整个 API。理解这一点是读懂整个数据模型的关键:
- 内存数据库:testing/mocks/db.ts 基于
@mswjs/data工厂定义user/team/discussion/comment四张"表",与 types/api.ts 的实体一一对应; - 持久化:Node 环境下写入
mocked-db.json,浏览器环境存入localStorage(键名msw-db),因此刷新页面数据不丢失;persistDb在NODE_ENV === 'test'时跳过写盘,保证测试的隔离性; - Handler 集合:testing/mocks/handlers 下按资源拆分(
auth.ts、teams.ts、discussions.ts、comments.ts、users.ts),路由统一以env.API_URL为前缀; - 独立 Mock 服务器:根目录的 mock-server.ts 让 Mock API 可以在 Node 进程中独立启动,前端应用照常发起 HTTP 请求,与连接真实后端时的行为完全一致。
这套机制意味着:文档中描述的每一个实体、每一条权限规则,都能在 Mock 层找到可执行的代码证据;同时前端代码只面向API_URL编程,替换为真实后端时应用代码几乎无需改动。
小结
Bulletproof React 的示例应用体量很小,但数据模型设计得相当"讲究":BaseEntity统一了 ID 与时间戳,Meta统一了分页协议,角色权限同时落在前端策略表与 Mock 服务端两处,注册流程则用 Zod 联合类型把业务规则固化为可校验的契约。以 docs/application-overview.md 为入口,配合本文引用的类型定义、Mock 数据服务与授权实现,可以完整看到"业务需求 → 数据模型 → API 契约 → 前端实现"的落地图景;更深层的目录组织、API 分层、测试与安全实践,可继续阅读 Project Standards、Project Structure 与 API Layer 等配套文档。
【免费下载链接】bulletproof-react🛡️ ⚛️ A simple, scalable, and powerful architecture for building production ready React applications.项目地址: https://gitcode.com/GitHub_Trending/bu/bulletproof-react
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考