首页 / 资讯中心 / 文章详情

t3code实战:用T3 Stack一键生成全栈CRUD代码

t3code实战:用T3 Stack一键生成全栈CRUD代码 ★ FEATURED ARTICLE
做全栈开发这些年我最烦的就是新项目开工时那套重复劳动初始化脚手架、配 TypeScript、接数据库 ORM、写 CORS、调 ESLint。这些事不是难而是琐碎每次都要花掉一两个小时还容易在版本上踩坑。t3code 的出现算是把我从这部分杂活里解放出来了——它不是普通的脚手架而是基于 T3 Stack 的一套代码生成工具默认帮你组装好 Next.js、TypeScript、tRPC、Prisma 和 Tailwind并且能按需生成 Router、Schema、类型定义和 CRUD 模板。这篇文章就把我这段时间用 t3code 的完整体验写出来从环境准备到生成代码再到避坑一条龙讲透。这篇文章适合两类人一是刚接触 T3 Stack、想从零快速搭起一个全栈项目的开发者二是已经在手动写 tRPC 和 Prisma、但嫌重复劳动太多的老手。不同基础的读者都能从这里拿到可以直接抄的配置和命令少走弯路。1. t3code 项目定位与技术选型解读1.1 为什么会有这样一个工具先把背景说清楚。T3 Stack 是近年来前端圈子里很火的一个技术组合核心成员包括 Next.js、TypeScript、tRPC、Tailwind CSS再加一个 Prisma 作为数据库工具。组合本身很优秀但问题也出在这优秀的东西往往门槛高你得自己把这几样东西串起来。装依赖、写 tsconfig、配置 Tailwind、初始化 Prisma、定义 tRPC 路由每一步都得仔细一步错就报一堆类型错误。t3code 的目标就是把这套串联工作标准化。它做的事情可以概括成两件第一用交互式命令替你完成项目初始化生成一套已经配置好的 T3 Stack 项目骨架第二提供代码生成能力让你在后续开发中不用每次手写 tRPC Router 和 Prisma Model 的样板代码直接执行一条命令就能把带类型安全的代码模板生成出来。我最初对这类工具是持谨慎态度的。因为很多代码生成器生成的代码太死一旦要改就得全改反而比手写还麻烦。但 t3code 的设计思路不太一样它生成的不是不可维护的黑盒代码而是基于模板的可读代码——生成之后你照样可以改它只是把最重复的那 80% 先替你写了。1.2 T3 Stack 到底解决了什么问题要理解 t3code 的价值得先理解 T3 Stack 本身。很多初学者第一次看到 tRPC 这个词都懵这很正常。用生活里的话说传统前后端联调像是两个人分别在两张纸上写字再互相拍照发给对方看偶尔还有传丢传错的。tRPC 则是两个人共用一本同步更新的笔记本前端定义一个函数后端立刻就有对应的类型提示不用再手动维护一份 API 文档和一堆接口类型。Next.js 负责页面和 API 路由Tailwind 负责样式Prisma 负责数据库建表和查询tRPC 负责类型安全的接口调用TypeScript 负责把这些全都串在类型系统里。这套组合的好处是全程端到端类型安全从数据库字段到前端 UI中间的每一层都有类型把关改动字段名时编辑器会直接标红提醒你哪里漏了。但代价也很明显这套东西的配置项非常多。以 Prisma 为例光 datasource、generator、model 三个模块的配置就能写出一堆坑来稍不注意就出现PrismaClientInitializationError。而 t3code 正是把这一套最佳实践固化成了模板让新手也能按规范的方式起步。1.3 t3code 的定位差异脚手架之外它还做代码生成市面上很多脚手架比如 create-react-app、create-next-app核心能力是初始化那一刻。项目创建完脚手架的任务就结束了后续所有业务代码都得自己写。t3code 不一样它的核心能力在于持续生成。我实际用下来的感受是它像一个内置了全栈最佳实践的代码助手。当你需要新增一个业务模块时执行t3code generate model和t3code generate router它会自动创建 Prisma Model、tRPC Router、Zod Schema 以及基础的 CRUD 接口还会顺手帮你把类型文件更新了。这些代码都有明确的注释不是那种加密过一样的压缩代码完全可以二次编辑。这决定了 t3code 适合的场景中小型全栈项目、内部管理系统、个人项目快速原型。如果项目已经高度定制化或者团队有自己的一套框架规范那用它反而不合适。工具嘛选对了场景才是好的。2. 环境准备与项目初始化实操2.1 前置环境清单在跑 t3code 之前先检查一下本机环境。我把常用的版本列在这里依赖建议版本说明Node.js 18.17低于这个版本Next.js 14 和 tRPC v10 会有兼容问题pnpm 8.0t3code 默认使用 pnpm 作为包管理器速度快且能处理依赖提升问题git任意较新版本初始化时自动创建 git 仓库Docker可选任意本地跑 PostgreSQL 数据库时很好用这里特别强调一下 pnpm。如果你习惯用 npmt3code 在交互式初始化时会问你是否使用 pnpm我建议不要跳过。pnpm 对 Next.js 的依赖管理更干净尤其是避免幽灵依赖导致的运行时错误。相信我在 pnpm 上踩过的坑比你想的要多。2.2 一条命令创建项目环境准备好后执行npx create-t3codelatest my-app命令执行后会出现一个交互式界面问你要不要启用这些模块? Select which modules to include: [x] Next.js [x] TypeScript [x] tRPC [x] Prisma [x] Tailwind CSS [ ] NextAuth.js我的习惯是做内部工具或原型项目时先把 NextAuth 关掉等需要登录功能时再手动加。因为 NextAuth 的配置项多对数据库表结构也有要求初期引入容易分心。核心五件套Next.js、TypeScript、tRPC、Prisma、Tailwind全开这个组合最稳。还有一步它会问你数据库类型默认是 SQLite也支持 PostgreSQL 和 MySQL。如果是做简单DemoSQLite 零配置最省事。如果准备做正式项目选 PostgreSQL 准没错——这个决定最好一开始就做好后面换数据库时要重新生成 Prisma 迁移挺麻烦的。2.3 初始化后的目录结构初始化完成后项目结构大致如下my-app/ ├── src/ │ ├── pages/ # Next.js 页面路由 │ ├── server/ │ │ ├── api/ # tRPC API 路由入口 │ │ ├── db/ # Prisma client 实例 │ │ └── api/ │ │ ├── routers/ # tRPC 子路由 │ │ ├── root.ts # 根路由聚合 │ │ └── trpc.ts # tRPC 上下文与初始化 │ └── utils/ ├── prisma/ │ └── schema.prisma # 数据库模型定义 ├── tailwind.config.ts ├── tsconfig.json └── package.json这个结构和官方 create-t3-app 基本一致说明 t3code 是在 T3 官方最佳实践之上做的增强不是另起炉灶。这也意味着你在社区搜到的很多 tRPC 教程、Prisma 教程在这里都适用学习成本没有浪费。首次进入项目后先跑pnpm install再跑npx prisma db push把初始模型同步到数据库。t3code 初始化模板里已经内置了一个示例模型比如用户和帖子你可以先跑通一次完整的 CRUD 循环再开始删改。3. 核心功能拆解三种高频代码生成场景3.1 生成 tRPC Router从手写到模板化t3code 的代码生成命令格式很统一基本是t3code generate 类型 名称。我先从最常用的 Router 说起。假设你现在想给文章模块写一个 tRPC Router不需要手动建文件执行npx t3code generate router post它会自动在src/server/api/routers/post.ts生成一个文件。生成的初始代码长这样import { createTRPCRouter, protectedProcedure, publicProcedure } from ~/server/api/trpc; import { z } from zod; export const postRouter createTRPCRouter({ list: publicProcedure.query(async ({ ctx }) { const items await ctx.prisma.post.findMany({ orderBy: { createdAt: desc }, }); return items; }), getById: publicProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) { return await ctx.prisma.post.findUnique({ where: { id: input.id }, }); }), create: protectedProcedure .input(z.object({ title: z.string().min(1), content: z.string() })) .mutation(async ({ ctx, input }) { return await ctx.prisma.post.create({ data: { title: input.title, content: input.content, authorId: ctx.session.user.id, }, }); }), });注意它默认生成了三种 procedurepublicProcedure不需要登录即可访问protectedProcedure需要登录。这个区分很实用因为实际项目中绝大多数查询都是公开的而写操作基本都要求登录。它这么做正好逼着你一开始就思考接口的访问权限而不是等上线后发现接口裸奔。生成之后别急着完事你需要手动把postRouter注册到src/server/api/root.ts里。这一步 t3code 没有做成全自动原因是注册根路由涉及模块依赖关系自动改容易出错。手动加上这行整个 Router 就算通了import { postRouter } from ~/server/api/routers/post; export const appRouter createTRPCRouter({ post: postRouter, });前端调用时直接体验一把类型安全const { data } api.post.list.useQuery(); api.post.create.useMutation();不需要自己定义接口类型前端拿到的每条数据都带着 Prisma 生成的类型。改一次 Prisma 模型前后端类型全部同步更新这就是当初选这套栈最值的地方。3.2 Prisma Schema 生成与模型同步Router 生成之后紧接着要做的是创建对应的数据模型。手动写 Prisma Model 其实不难难的是搞清楚字段类型、关系、索引和默认值。t3code 这里提供的生成命令是npx t3code generate model post执行前它会问你这个模型包含哪些字段支持的类型包括字符串、整数、布尔值、日期时间和枚举。同样以 post 为例完整一点的生成结果model Post { id String id default(cuid()) title String content String published Boolean default(false) authorId String author User relation(fields: [authorId], references: [id], onDelete: Cascade) createdAt DateTime default(now()) updatedAt DateTime updatedAt }这里有几个细节值得说明。default(cuid())比自增整数更适合分布式环境和 URL 暴露场景因为 id 不可枚举猜不透别人的数据量。onDelete: Cascade表示删除用户时级联删除其所有文章这个选择要看业务需求如果你的文章需要保留归档那这里应该改成SetNull并让 authorId 可空。模型定义完成后执行npx prisma db push npx prisma generate因为 t3code 生成的模型和 Router 是配套的所以执行完这两行后刚才postRouter里的ctx.prisma.post就能正确识别所有字段了。我在使用中还发现一个技巧当 Prisma 模型改多了以后先用npx prisma format格式化一下 schema 文件再 push能省下不少因为格式错乱导致的报错。3.3 Zod Schema 与类型安全表单校验第三个高频场景是 Zod Schema。tRPC 里每一项input校验都依赖 Zod但很多人在写表单校验时仍然手写if (title.length 1)这种土方法。t3code 针对这个场景提供了一个便捷命令直接生成与 Prisma Model 字段对齐的 Zod Schemanpx t3code generate schema post生成的 schema 会被放在一个统一的src/server/api/schemas/post.ts文件里并和 Router 的 input 做映射。它的价值在于Zod Schema、Prisma Model、tRPC 类型三者保持同步生成不会出现数据库允许为空、接口层却校验必填这种低级错误。比如生成的校验规则会对齐字段约束import { z } from zod; export const createPostSchema z.object({ title: z.string().min(1, 标题不能为空).max(100, 标题最多100字), content: z.string().min(1), published: z.boolean().default(false), }); export type CreatePostInput z.infertypeof createPostSchema;z.infer这个操作值得多说两句它把 Zod Schema 反推成了 TypeScript 类型导入到前端后表单的onSubmit参数就能自动获得推断类型不用手动写 interface。这是类型安全闭环的重要一环。当然校验规则不可能完全自动生成。比如 title 最大长度是 100 还是 200这属于业务规则工具只能用 Prisma 的类型和长度约束给出默认判断。生成后你仍然需要根据业务去微调规则。工具能替你完成的是骨架和 90% 的体力活剩下的 10% 业务判断是省不掉的这反而是好事。4. 一个完整示例用户管理模块 CRUD4.1 需求梳理与准备讲完单独功能我用一个完整的用户管理模块把整个流程串起来。需求也很常见管理员后台需要查看用户列表、创建用户、重置用户密码、删除用户。这个模块完全可以用 t3code 的生成能力快速搭出雏形。先把用户模型补全因为默认模板里的 User 模型比较简略我会手动添加一个role字段用于区分管理员和普通用户。enum Role { ADMIN USER } model User { id String id default(cuid()) name String? email String unique role Role default(USER) createdAt DateTime default(now()) }值得注意的是如果你要在这个项目上接 NextAuth这个 User 模型还需要有accounts和sessions关系这个要小心处理。t3code 的开发人员在这个地方做了个贴心处理当你执行t3code generate model user并检测到项目已启用 NextAuth 时它会自动补全关联字段避免你回去手写一堆容易出错的关系。4.2 用 t3code 生成整套 CRUD 模板模型更新后在prisma/schema.prisma里同步好 Model执行数据库同步npx prisma db push npx prisma generate接着用 t3code 生成一个用户管理的 Routernpx t3code generate router user --crud注意这个--crud参数它会生成比默认模板更完整的增删改查方法包括list、getById、create、update、delete五个方法。逐个看下关键部分update: protectedProcedure .input(z.object({ id: z.string(), name: z.string().optional(), role: z.enum([ADMIN, USER]).optional(), })) .mutation(async ({ ctx, input }) { return await ctx.prisma.user.update({ where: { id: input.id }, data: { name: input.name, role: input.role, }, }); }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) { return await ctx.prisma.user.delete({ where: { id: input.id }, }); }),这里有个权限问题必须要自己补一段逻辑现在任何登录用户都能调用update和delete更新别人的资料这显然不行。我一般在update前加一个判断只有ADMIN角色能操作或者id ctx.session.user.id时允许用户更新自己的信息。update: protectedProcedure .input(...) .mutation(async ({ ctx, input }) { if (ctx.session.user.role ! ADMIN ctx.session.user.id ! input.id) { throw new TRPCError({ code: FORBIDDEN, message: 无权操作 }); } // 省略后续更新逻辑 });t3code 不会替你写业务权限但它生成protectedProcedure已经守住了未登录不能调用的底线。至于登录了但没权限这种场景就得靠你自己的业务判断来补强。4.3 前后端联调与细节优化后端 Router 生成后前端调用非常顺手。在 React Hook 里直接用const users api.user.list.useQuery(); const updateUser api.user.update.useMutation({ onSuccess: () users.refetch(), });这里有一个小设计值得分享t3code 生成的update方法中入参类型已经解析好了Prisma.UserUpdateInput但它在生成输入的时候故意改成可选项避免前端需要把整条用户数据原样回传的情况。这种细节说明工具作者是真做过实际项目不是纸上谈兵。很多初学者写更新接口时会把整个对象回传这在真实项目中既不安全也没效率。联调时我还会用 tRPC 的isLoading和isFetching区分初次加载和后台请求。很多 UI 库的 loading 态混乱就是因为没有区分这两者。第一次进页面时用isLoading展示骨架屏后续操作时用isFetching控制按钮禁用体验会细腻很多。5. 常见问题与坑位速查5.1 数据库连接失败t3code 搭配 SQLite 时很少出问题问题主要集中在 PostgreSQL。新建的 Postgres 数据库如果是在 Docker 里跑的连接串一定要核对 host 和端口DATABASE_URLpostgresql://user:passwordlocalhost:5432/mydb?schemapublic常见错误是PrismaClientInitializationError十有八九是连接串里用户名密码写错或者数据库还没创建成功。还有一个坑如果你在docker-compose.yml里配置了数据库但在容器内又映射了不同的端口本地连接时端口写错也会报这个错。排查顺序建议是先docker ps确认容器在跑再试连接串最后才看代码。5.2 Prisma 版本与生成类型不同步这是使用 t3code 时最隐蔽的问题。当 Prisma 模型改动后如果你只跑了prisma db push而忘了再跑npx prisma generate前端 TypeScript 不会报错但运行时ctx.prisma.user.findMany()可能拿不到新字段的类型。这两个命令的区别在于db push把 schema 同步到数据库generate更新 client 的 TypeScript 类型。只执行前者数据库变了但你的类型文件没更新只执行后者反过来。我的习惯是每次改完 schema必须连着执行npx prisma format npx prisma db push npx prisma generate三个命令一条龙缺一不可。如果用了 Prisma 的 migration 工作流那就把中间那步换成npx prisma migrate dev --name 描述。5.3 端口冲突与环境变量Next.js 开发服务器的默认端口是 3000但如果你同时开了多个前端项目或者有别的服务占用了 3000 端口启动时会直接报EADDRINUSE。解决方法很简单在启动命令里改端口pnpm dev -- -p 3001还有一种情况是.env文件里的DATABASE_URL被误删或没创建。t3code 初始化模板里会生成一个.env.example第一次使用时最好复制一份为.env再填值。很多报错其实都源于环境变量缺失而不是代码问题。5.4 tRPC 类型报错使用 t3code 生成的 Router 后前端经常出现的一个类型报错是Unable to infer type from context。这个问题通常是因为在trpc.ts里创建的createTRPCContext返回的prisma实例类型不匹配。检查一下src/server/db.ts里导出的是不是PrismaClient实例并且trpc.ts里引用的是同一个实例。它是单例模式不能每次请求重新new PrismaClient()否则会出现连接数爆炸还会导致类型推断互相不认。另外一个常见问题是修改 Router 后前端类型没有立即更新。tRPC 的类型是通过 TypeScript 推断的理论上改完后端前端保存一下就能同步。如果没同步重启一下 dev server 基本都能解决。5.5 生成代码后的二次修改注意事项最后提醒一下t3code 生成代码后如果你做了大量手改后续再执行同名的generate命令它不会智能合并而是直接覆盖。我在早期的项目里踩过一次改了不少业务逻辑的 Model 被一个覆盖命令打回原形。后来我的做法是生成后把 Router 文件里的核心业务逻辑全部迁移出生成区域让生成的文件只保留薄薄的一层 CRUD 壳子。这样即使被覆盖损失也在可控范围内。6. 我的一些使用心得与扩展建议用 t3code 跑了两个项目之后我对它的定位有了更清晰的认识它是一个非常好的全栈项目起步工具也是一个平时开发中能持续调用的代码助手但它不是万能代码生成器。它的价值在于帮你把 T3 Stack 的最佳实践固化成可复用的模板让你把精力花在业务逻辑和产品体验上。按照个人经验新手用 t3code 的最好路径是先用默认模板完整跑一个 Demo理解 tRPC 的请求链路和 Prisma 的类型传导再尝试用 generate 命令生成一个新的业务模块最后再逐步加权限、加校验。不要一上来就改它生成的模板内部结构否则你很难判断问题出在工具还是自己代码里。最后分享一个小技巧如果团队协作建议把生成代码的规范写进 README包括命令格式、模型命名习惯、是否允许手改生成区。t3code 本身已经帮你把重复劳动压缩到最低了剩下的就是让团队在使用时保持约定一致。毕竟工具是死的流程才是活的把流程理顺了开发效率的提升会远超你装几个脚手架带来的那点收益。
阅读完成 · 觉得有帮助?
咨询建站