资讯动态

t3code详解:T3 Stack全栈开发中的类型安全代码生成器

发布时间:2026/10/26 8:38:40 来源:尧图企业网站定制
在技术社区逛久了你会发现T3这个词出现的频率越来越高。它既不是某个框架的新版本号也不是什么算法代号而是指一套在TypeScript生态里相当流行的全栈开发方案——T3 Stack。而t3code在我看来正是围绕这套方案衍生出的一个非常有意思的工具或项目代号。第一次接触t3code我脑子里冒出的第一个问题是这到底是个脚手架还是一套代码生成器后来在实操中逐渐清晰起来——它解决的核心痛点是让开发者从一个业务需求出发直接生成一套类型安全、结构统一的全栈代码骨架。换句话说你不需要再花半天时间在Next.js项目里手动接线Prisma、tRPC和Zodt3code帮你把这些繁琐但必须的管道工程全部做掉。这篇文章我想以一个实际使用者的身份把t3code这套东西从设计思路、核心配置到常见坑位完整拆开揉碎讲一遍。无论你是刚接触 T3 Stack 的新手还是已经在用但想优化流程的老手这篇内容应该都能给你一些参考。1. 项目定位与整体设计思路先说结论t3code本质上是一个面向 T3 Stack 生态的代码生成与项目初始化工具。它的设计目标不是发明新框架而是把 T3 Stack 里那些最佳实践固化下来形成一套可复现、可扩展的工程模板。1.1 T3 Stack 的核心理念与 t3code 的切入点T3 Stack 是由create-t3-app推广开来的技术组合标准成员包括Next.js作为应用框架负责页面渲染、路由和 API 路由。TypeScript全栈类型系统的基础从后端到前端贯穿到底。Tailwind CSS原子化 CSS 方案解决样式问题。tRPC端到端类型安全的 API 调用方案免去手写 REST 或 GraphQL 的接口层。PrismaORM 层负责数据库建模和迁移。NextAuth.js认证方案与 Next.js 深度集成。这套组合的最大卖点是类型安全。后端定义了一个查询函数前端useQuery里立刻就能拿到完整的类型推断字段名写错了编译期直接报错。但这种威力是有代价的——接线成本高。我第一次用create-t3-app初始化项目后光是配置tRPC Router的上下文、把Prisma Client挂到请求上下文里、处理NextAuth的 Session 类型就花了小半天。而且每个人接线的习惯不同有人喜欢把路由拆成多个文件有人喜欢全塞一个文件里项目一大就会变得混乱。t3code的切入点就在这里它不只是初始化脚手架而是根据你定义的业务模型比如一张数据库表、一个 API 查询自动生成完整的代码链路。1.2 为什么选择代码生成而不是手动配置我见过不少团队在项目刚开始时手动接线前几个接口还好到后面平均每写一个 CRUD 就要重复以下操作写PrismaModel跑迁移。写tRPCRouter处理输入校验。写前端 API 调用封装。写 React Query 的 Hook。手动对齐所有类型。这五个步骤每一步都有出错的可能。字段名拼错、类型不匹配、校验规则忘了加这些在运行时才会暴露的问题浪费了大量调试时间。t3code做的事情就是把这五步压缩成一步。它读取你写好的模型定义文件或者通过 CLI 交互式输入然后自动生成 Prisma Model。自动生成 tRPC Router 和输入校验 Schema。自动生成前端类型安全的 API 调用函数。自动生成 React Query 的 Hooks。你只需要在生成的代码基础上补充业务逻辑比如权限校验、额外判断而不是从零手写样板代码。1.3 项目整体架构设计从架构层面看t3code生成的代码遵循经典的src/目录结构但有几个值得注意的设计决策src/ ├── pages/ # Next.js 页面路由 ├── server/ │ ├── api/ # tRPC Router 定义 │ ├── db/ # Prisma Client 实例 │ ├── auth/ # NextAuth 配置 │ └── trpc/ # tRPC 上下文和 Root Router ├── styles/ # Tailwind 入口 └── utils/ ├── api.ts # tRPC 客户端封装 └── trpc.ts # tRPC Hooks这里有几个细节值得展开。第一服务端代码和客户端代码严格分离。所有数据库操作、业务逻辑都放在server/目录下前端通过utils/api.ts里的封装来调用。这不仅是目录整洁的问题更重要的是避免在客户端 bundle 里意外打入服务端代码。第二tRPC Router 的拆分策略。t3code默认生成一个根 Router然后在根 Router 下按业务域拆分子 Router。比如postRouter、userRouter而不是把所有接口写在一个文件里。这种拆分在项目变大后优势非常明显合并冲突少、每个文件职责单一、容易做权限隔离。第三类型推断链路的完整性。从数据库模型到 API 输出再到前端 Hook整条链路都是类型安全的。这意味着你在Post模型里加了一个字段跑一遍t3code前端代码里所有用到这个模型的地方类型都会自动更新。2. 核心功能拆解与技术要点明确了定位之后下面拆解t3code的核心功能点。我用一个实际场景来说明假设我们要做一个博客系统需要支持文章的增删改查并且文章有标题、内容和标签三个字段。2.1 模型定义一切从 Schema 开始t3code的入口是模型定义。它支持两种方式方式一交互式 CLI运行t3code init后CLI 会一步步问你模型叫什么名字有哪些字段每个字段是什么类型哪些字段是必填的哪些字段需要唯一约束方式二直接写配置文件我更推荐这种方式因为可重复、可版本控制。你创建一个models/post.ts文件import { defineModel } from t3code; export const PostModel defineModel({ name: Post, fields: [ { name: title, type: string, required: true }, { name: content, type: text, required: true }, { name: tags, type: string[], required: false, defaultValue: [] }, { name: published, type: boolean, required: false, defaultValue: false }, ], relations: [], });这里有个重要的设计t3code的模型定义并不是 Prisma Schema 的简单映射。它是更高层的抽象隐藏了 Prisma 的一些细节比如default的写法、字段类型映射让非 Prisma 专家也能上手。但如果你想要更细粒度的控制它也提供了raw选项可以直接写 Prisma 原生语法export const PostModel defineModel({ name: Post, fields: [...], raw: { index([published]), map(posts), }, });这个设计我挺喜欢。它给你一条从抽象到底层的渐进通道而不是永远锁死在高层抽象里。2.2 自动生成 Prisma Schema 与迁移文件定义好模型后执行t3code generate它会做以下几件事把模型定义转换成schema.prisma里的完整 Model。生成对应的 Migration SQL 文件。更新 TypeScript 类型定义。以我们上面的Post模型为例生成的 Prisma Schema 大概长这样model Post { id String id default(cuid()) title String content String db.Text tags String[] published Boolean default(false) createdAt DateTime default(now()) updatedAt DateTime updatedAt }注意到几个细节主键默认用cuid()而不是自增整数。这是 T3 Stack 生态的惯例。自增整数在某些场景下会暴露业务数据量而且分布式环境下容易冲突。cuid生成的是 URL 安全的字符串无序、不可猜测更适合作为公开接口的 ID。文本字段自动映射到db.Text。如果直接在 Prisma 里写String默认是VARCHAR(191)在 MySQL 下存长文会出问题。t3code在模型层帮你做了这个映射避免踩坑。时间戳字段自动补充。你不需要手动声明createdAt和updatedAt生成器会自动加上。这对统一规范很有帮助。2.3 tRPC Router 与输入校验的生成这是t3code最核心也最出彩的部分。对于Post模型它会自动生成一套完整的 CRUD Routerimport { z } from zod; import { createTRPCRouter, protectedProcedure, publicProcedure } from ~/server/api/trpc; import { PostService } from ~/server/services/PostService; // 输入校验 Schema 自动生成 const createPostSchema z.object({ title: z.string().min(1).max(200), content: z.string().min(1), tags: z.array(z.string()).optional().default([]), }); const updatePostSchema createPostSchema.partial(); export const postRouter createTRPCRouter({ list: publicProcedure .input(z.object({ page: z.number().int().positive().default(1), pageSize: z.number().int().positive().max(50).default(10), })) .query(async ({ ctx, input }) { return PostService.list(ctx.db, input); }), byId: publicProcedure .input(z.object({ id: z.string().cuid() })) .query(async ({ ctx, input }) { return PostService.getById(ctx.db, input.id); }), create: protectedProcedure .input(createPostSchema) .mutation(async ({ ctx, input }) { // 默认只允许登录用户创建 return PostService.create(ctx.db, { ...input, authorId: ctx.session.user.id, }); }), update: protectedProcedure .input(z.object({ id: z.string().cuid(), data: updatePostSchema })) .mutation(async ({ ctx, input }) { return PostService.update(ctx.db, input.id, input.data); }), delete: protectedProcedure .input(z.object({ id: z.string().cuid() })) .mutation(async ({ ctx, input }) { return PostService.delete(ctx.db, input.id); }), });这里有几个非常关键的细节Procedure 的权限分级。公开接口用publicProcedure需要登录的用protectedProcedure需要管理员权限的可以用adminProcedure。这是t3code的默认策略——读接口公开写接口保护。你可以手动调整但默认值就够安全。输入校验全部用 Zod Schema 表达。这不仅是校验更重要的是类型推导。前端调用createPost时input的类型就从createPostSchema推导出来了不需要额外声明。业务逻辑被抽到了 Service 层。这是我特别认可的一点。很多人写 tRPC 会把业务逻辑直接写在 Router 回调里但t3code生成了PostService类把数据库操作封装在 Service 里Router 只做参数校验和权限控制。逻辑清晰也方便写单元测试。2.4 前端类型安全 API 封装生成器不仅处理服务端还会自动生成前端调用代码。你可以直接这样用import { api } from ~/utils/api; function PostList() { const { data, isLoading } api.post.list.useQuery({ page: 1, pageSize: 10 }); if (isLoading) return div加载中.../div; return ( ul {data?.items.map((post) ( li key{post.id}{post.title}/li ))} /ul ); } function CreatePostButton() { const ctx api.useUtils(); const createMutation api.post.create.useMutation({ onSuccess: () { ctx.post.list.invalidate(); }, }); return ( button onClick{() createMutation.mutate({ title: 新文章, content: 内容, tags: [tech] })} 创建 /button ); }这段代码最爽的地方在于api.post.create.useMutation的入参类型完全由后端 Schema 推导后端加了字段前端这里会立刻类型报错你根本不可能忘掉传新字段。这是类型安全链条落到实处的体验比看多少文档都直观。3. 实操过程与核心环节实现理论说了不少下面进入实操环节。我用一个干净的 Linux/Mac 环境从零开始跑一遍完整的t3code流程并针对每一步标注关键注意点。3.1 环境准备与初始化先确保本机环境满足以下要求Node.js 18推荐 20 LTSpnpm 8T3 Stack 官方推荐 pnpm因为依赖安装更快、磁盘占用更小一个可用的数据库本地 MySQL/PostgreSQL或者 Docker 起的数据库都可以我是用 pnpm 初始化的pnpm create t3-applatest my-blog这里你可能会问这不是create-t3-app吗跟t3code什么关系关系是t3code作为create-t3-app可选安装的插件/扩展模块存在。基础模板还是 T3 App 的骨架但初始化完成后安装t3codepnpm add -D t3code安装完成后直接初始化配置pnpm t3code init这一步会在根目录生成一个t3code.config.ts配置文件。我的配置长这样import { defineConfig } from t3code; export default defineConfig({ modelsDir: ./models, databaseProvider: mysql, httpMethod: mutation, // or query控制生成调用方式 validation: zod, });注意t3code init默认会读取你已有的schema.prisma文件将其转换为内部的模型定义格式。如果你的项目已经有数据库模型了这一步会自动迁移不会覆盖你已有模型而是生成一个新的模型文件需要你手动确认合并。3.2 编写模型定义并生成代码我的models/post.ts文件前面已经展示过了。这里我补充一个关联关系的例子因为博客系统必然涉及用户和文章的关系import { defineModel } from t3code; export const PostModel defineModel({ name: Post, fields: [ { name: title, type: string, required: true }, { name: content, type: text, required: true }, { name: tags, type: string[], required: false, defaultValue: [] }, { name: published, type: boolean, required: false, defaultValue: false }, { name: author, type: relation, target: User, relationName: PostAuthorRelation }, ], relations: [ { type: many-to-one, field: author, target: User, onDelete: Cascade }, ], });注意relation字段类型的写法。t3code要求关联关系必须同时在fields和relations两侧声明这样它才能正确生成 Prisma 里的relation()语法。执行生成pnpm t3code generate生成过程是交互式的它会询问你是否要强制执行默认安全模式如果检测到已有代码文件会跳过是否要同时生成迁移文件我把所有选项都选了然后等它跑完。生成完成后目录结构里会多出以下文件src/ ├── server/ │ ├── services/ │ │ └── PostService.ts │ └── api/routers/ │ └── post.ts └── utils/ └── generated/ └── post.tsPostService.ts是全套生成的代码里最有含金量的部分。它包含了完整的 CRUD 数据库操作并且按照最佳实践做了分页、按 ID 查询等处理。我摘录一段import { type PrismaClient, type Post } from prisma/client; export class PostService { static async list( db: PrismaClient, params: { page: number; pageSize: number; search?: string } ): Promise{ items: Post[]; total: number } { const where params.search ? { OR: [ { title: { contains: params.search } }, { content: { contains: params.search } }, ], } : {}; const [items, total] await db.$transaction([ db.post.findMany({ where, skip: (params.page - 1) * params.pageSize, take: params.pageSize, orderBy: { createdAt: desc }, }), db.post.count({ where }), ]); return { items, total }; } static async create( db: PrismaClient, data: OmitPost, id | createdAt | updatedAt ): PromisePost { return db.post.create({ data }); } // ... update, delete, getById 类似 }这里用了$transaction来做查询 计数避免分页列表在并发场景下出现数量不一致的问题。这是 Prisma 社区公认的推荐写法t3code直接帮你放进了生成逻辑里不用自己踩坑。3.3 注册 Router 并启动应用生成完代码后还需要一步手动操作把生成的路由挂载到根 Router 上。打开src/server/api/root.ts把postRouter加进去import { postRouter } from ~/server/api/routers/post; import { createTRPCRouter } from ~/server/api/trpc; export const appRouter createTRPCRouter({ post: postRouter, // 其他 Router... }); export type AppRouter typeof appRouter;这一步设计得比较保守t3code没有直接改写你的root.ts文件而是生成的 Router 文件放到明确的目录里由你手动挂载。原因是root.ts是整个应用的路由枢纽自动改写容易产生冲突。我认为这保留了最终控制权是好事。一切就位后启动开发服务器pnpm dev如果一切正常你会看到 Next.js 应用跑起来。默认端口是 3000。这时打开浏览器访问http://localhost:3000/api/trpc/post.list?input{page:1,pageSize:10}需要在浏览器里把括号编码你能直接看到返回的 JSON 数据。这说明 tRPC 端点已经正常工作。注意如果你是第一次跑 T3 生态的项目很可能会在启动时遇到TypeError: Cannot read properties of undefined (reading prototype)之类的报错。这通常是tanstack/react-query版本和trpc/*版本不匹配导致的。解决办法是把版本都升到最新pnpm up trpc/* tanstack/react-query。3.4 前端页面接入生成代码我写一个简单的列表 创建 删除的完整页面验证全链路import { useState } from react; import { api } from ~/utils/api; export default function PostManager() { const [title, setTitle] useState(); const [content, setContent] useState(); const [page, setPage] useState(1); const { data, isLoading } api.post.list.useQuery({ page, pageSize: 5 }); const ctx api.useUtils(); const createPost api.post.create.useMutation({ onSuccess: async () { setTitle(); setContent(); await ctx.post.list.invalidate(); }, }); const deletePost api.post.delete.useMutation({ onSuccess: async () { await ctx.post.list.invalidate(); }, }); if (isLoading) return div加载中.../div; return ( div classNamep-8 h1 classNametext-2xl font-bold文章管理/h1 div classNamemy-4 flex gap-2 input value{title} onChange{(e) setTitle(e.target.value)} placeholder标题 classNameborder p-2 / input value{content} onChange{(e) setContent(e.target.value)} placeholder内容 classNameborder p-2 / button onClick{() createPost.mutate({ title, content })} classNamebg-blue-500 px-4 py-2 text-white rounded 创建 /button /div ul {data?.items.map((post) ( li key{post.id} classNameflex justify-between border-b py-2 div p classNamefont-semibold{post.title}/p p classNametext-sm text-gray-500{post.content}/p /div button onClick{() deletePost.mutate({ id: post.id })} classNametext-red-500 删除 /button /li ))} /ul div classNamemt-4 flex gap-2 button disabled{page 1} onClick{() setPage((p) Math.max(1, p - 1))} classNameborder px-3 py-1 上一页 /button span classNamepx-3 py-1第 {page} 页/span button disabled{!data?.hasMore} onClick{() setPage((p) p 1)} classNameborder px-3 py-1 下一页 /button /div /div ); }第一次跑通这段代码的体验非常顺畅createPost.mutate的参数类型自动就是{ title: string; content: string }我想要传tags字段时 TS 不会报错因为它是可选的但如果我传了一个不存在的字段编译器立刻标红。这种被类型牵着走但不会走错路的感觉用惯了真的回不去。3.5 数据库迁移的执行细节t3code generate生成了迁移文件但默认不会自动执行。你需要手动运行 Prisma 的迁移命令让数据库结构真正落地pnpm prisma migrate dev --name init这个命令会做三件事对比schema.prisma和迁移目录找出变化。在新的迁移文件里记录变化 SQL。执行迁移并重新生成 Prisma Client。一个常见的坑是如果你直接改schema.prisma然后migrate dev而 Prisma 检测到schema.prisma与上一次迁移记录有冲突它会要求你重置数据库丢数据。而通过t3code生成的方式每次都是全新的、结构一致的迁移出厂顺序永远是对的从根上避免了这种Schema 漂移问题。如果你用的是 Docker 数据库可以这样快速起一个 MySQL 实例docker run --name mysql-dev -e MYSQL_ROOT_PASSWORDpassword -e MYSQL_DATABASEmyblog -p 3306:3306 -d mysql:8然后.env文件里配置DATABASE_URLmysql://root:passwordlocalhost:3306/myblog注意Prisma 8 及以上的版本要求DATABASE_URL必须使用mysql://前缀mysql://会触发自动的连接池配置不要手动加?connection_limit1之类的参数否则可能遇到连接池耗尽的问题。4. 常见问题与排查技巧实录实操过程中踩过的坑不少这里挑几个高频的按现象、原因、解决办法三栏整理成速查表。现象可能原因解决办法生成时提示PrismaSchemaError: Field id is missing模型定义里没有主键字段在字段列表加{ name: id, type: string, isId: true }或使用default(cuid())自动生成前端调用api.post.list.useQuery报类型不匹配重新生成代码后没有重启next dev进程必须重启开发服务器tsc增量编译有时不会立刻刷新.d.ts类型点击创建按钮后一直 loading控制台报 500tRPC Router 抛出异常最常见的又是数据库连接失败先看服务端日志若提示P1001说明DATABASE_URL连不上检查数据库服务和.env生成的PostService里 search 参数对中文查询无效MySQL 的LIKE默认对中文按字符匹配没有做分词对中文搜索建议引入全文索引或MeiliSearch或者先坚持前缀匹配更新页面数据后列表不刷新忘记调用ctx.post.list.invalidate()在useMutation的onSuccess里加await ctx.post.list.invalidate()4.1 生成代码被手动修改后无法重新生成这是t3code的一个设计特性安全模式。默认情况下如果目标文件已经存在并且内容与生成器模板不一致比如你手动改过它会跳过避免覆盖你的改动。但很多人在第一次跑t3code generate后改了生成的代码然后又要改模型重新生成发现生成的代码还是旧版。这时候需要强制重新生成pnpm t3code generate --force用了--force会覆盖所有生成的代码。强烈建议如果你在生成代码上做了自定义修改把修改抽到独立的封装文件里而不是直接改生成的代码。比如给PostService加额外方法时继承而不是修改// 新文件 PostServiceExt.ts export class PostServiceExt extends PostService { static async search(db: PrismaClient, keyword: string) { // 自定义搜索逻辑 } }这样重新生成时PostService.ts可以被安全覆盖而你的扩展逻辑住在另一个文件里互不干扰。4.2 中间件顺序导致的类型报错在 Next.js 里如果你写了middleware.ts做路由保护要注意 tRPC 的 endpoint 路径不能被中间件误伤。我之前遇到过一个情况中间件把所有/api/*的路由都要求认证导致publicProcedure的查询也被拦截了。排查思路是先看中间件匹配的路由列表matcher配置里是否有/api/trpc/:path*。确认publicProcedure和protectedProcedure的区分在 Router 层而不是中间件层。正确做法中间件只处理页面路由比如/dashboard、/settingsAPI 层的鉴权交给 tRPC 的 Procedure 去处理。从这个角度看t3code默认生成的publicProcedure公开接口、protectedProcedure登录接口就是一种去中心化的权限控制策略比中间件统一拦截要灵活得多。4.3 利用生成代码快速搭建复杂筛选列表CRUD 只是基础t3code更实用的场景是快速搭建带筛选的管理后台。我在一个内部工具里用t3code生成了订单列表然后在生成的list方法基础上扩展了多条件筛选const listSchema z.object({ page: z.number().default(1), pageSize: z.number().max(100).default(20), status: z.enum([PENDING, PAID, SHIPPED, COMPLETED]).optional(), dateRange: z.object({ start: z.date(), end: z.date() }).optional(), keyword: z.string().optional(), }); export const orderRouter createTRPCRouter({ list: publicProcedure.input(listSchema).query(...), });注意这里我没有用t3code的默认生成而是手写了listSchema因为筛选条件是不确定的。t3code的灵活之处就在于它生成的 Router 是标准模板你可以随时替换成自定义 Schema服务端框架本身不设限。4.4 认证流程与 Session 类型整合T3 Stack 用 NextAuth 做认证时最常见的类型痛点是对session.user.id的访问。默认的DefaultSession类型里没有id字段直接访问会报 TS 错误。t3code生成的代码里会自动声明类型补充// src/types/next-auth.d.ts import { type DefaultSession } from next-auth; declare module next-auth { interface Session { user: { id: string; } DefaultSession[user]; } }如果手动配过一次就知道这个文件有多容易被遗忘。生成器直接给你放好了而且保证类型文件和/types的引用路径正确省去一堆 import 探险。5. 从 t3code 到团队工程化实践用了一段时间后我发现t3code的价值不只是在个人项目里提升效率它对团队协作的规范作用也很明显。5.1 代码统一性带来的 Code Review 效率提升以前每个开发写接口都有自己的风格有人把校验写在 tRPC 里有人写在 Service 里有人直接在 Prisma 层用where糊了个筛选。代码 review 最耗时的往往不是逻辑本身而是适应别人的写法。t3code生成的代码把这些问题都规整了校验 Schema 统一在 Router 层、数据库操作统一在 Service 层、查询参数统一走 Zod。新同事看代码时不需要猜测这个接口为什么用这种写法因为所有接口的写法都一样。5.2 与 CI/CD 集成生成代码纳入自动化流水线更进一步我们可以在 CI 里加一步代码生成检查确保合并到主干的代码是可复现的# .github/workflows/t3code-check.yml name: t3code-check on: [pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: pnpm/action-setupv4 - uses: actions/setup-nodev4 with: node-version: 20 - run: pnpm install - run: pnpm t3code generate --check加了--check参数后t3code会以 dry-run 模式检查当前代码与模型定义是否一致。如果不一致说明有人改了模型但没跑生成CI 直接报错。这样保证了生成代码永远和生产环境一致不会出现我本地生成过但你拉下来没有的情况。5.3 从代码生成到文档生成这是t3code的另一个亮点由于模型定义了字段、类型、关系它可以顺带生成 API 文档。执行pnpm t3code docs会在docs/api.md下生成一份 Markdown 格式的接口文档包含每个 Router 的输入输出参数。虽然tRPC本身有 Swagger 类的方案但一个简单的 Markdown 文档对团队内部的快速沟通往往更高效——尤其是和非技术背景的同事对齐业务需求时。我试过把这份文档直接贴到 Notion 里当接口约定后端和前端的理解立刻同步少了很多来回解释的时间。写在最后的话回到t3code这个名字本身我一直觉得它是 T3 生态里一个被低估的补充工具。它没有重新发明轮子只是把 T3 Stack 里那些约定俗成的最佳实践变成了可生成、可重复、可校验的工程化产物。我个人的体会是代码生成器最容易做死的地方是生成的东西太死板一旦有复杂业务需求就绕不过去最后只能把生成的代码全部改掉等于白用。但t3code在设计上留了足够的自由度你可以自定义 Schema、自定义 Service、自定义扩展生成的只是骨架而不是枷锁。最后再分享一个小技巧如果你团队里有人对 T3 Stack 不熟直接让他上手写代码可能会被各种目录结构和类型定义绕晕。但给他一个t3code的模型配置文件让他从改字段开始配合生成器不断重新生成他能很快理解数据库结构和API 接口之间的映射关系。这比任何培训文档都直观。下次如果你还在为初始化一个类型安全的全栈项目而犹豫不妨试试先定义一个模型然后生成一整套链路看看。用熟了之后你会发现写后端接口的乐趣可能比想象中多一点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑