news 2026/10/28 16:12:35

t3code实战:用T3 Stack构建端到端类型安全的全栈应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
t3code实战:用T3 Stack构建端到端类型安全的全栈应用

1. t3code 是什么:一个把"类型安全"当默认值的全栈尝试

先说结论:t3code 是我最近折腾的一个全栈小项目,名字听着像"第三代代码",实际上它的内核是 T3 Stack——也就是 The T3 Stack 那套把 Next.js、TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth.js 六件套拧在一起的技术栈。把这个项目命名为 t3code,一半是为了纪念这套技术栈,另一半是因为它确实解决了我多年全栈开发里最头疼的一件事:类型信息在前端、后端、数据库之间传递时总是断掉。

如果你没听说过 T3 Stack,我用一句话解释:它不是一个新的框架,而是一套经过实践验证的组合拳。传统前后端分离项目里,后端写一套接口文档,前端手写一套类型定义,数据库模型再单独维护一套,三套东西全靠人肉同步。今天接口加个字段、明天数据库改个类型,总有某个环节漏更新,等到运行时才发现报错。T3 Stack 的核心思路是让 TypeScript 的类型从数据库一路"长"到前端组件里,中间没有任何断层。数据库表结构变了,Prisma 客户端类型自动变;服务端定义了新路由,前端调用的方法列表跟着变。类型不一致在编译期就直接暴露,而不是线上炸锅。

t3code 这个项目我刻意选了"带登录 + 权限 + 业务数据 CRUD + 简单仪表盘"的中后台场景。这个选择有讲究:它几乎覆盖了日常业务开发的全部关键节点——数据库建模、服务端接口、前端状态、用户鉴权、部署上线。你跟着这篇文章把它跑通,等于把 T3 应用从 0 到 1 完整搭了一遍,之后换到任何实际项目,改改业务表就能上手。

适合看这篇内容的人,我大致分三类:第一类是 React / Next.js 有一定基础、但一直没搞明白 tRPC 到底怎么工作的前端;第二类是写后端但想用一整套类型安全的 TypeScript 方案减少前后端联调成本的人;第三类是已经在用 create-t3-app 初始化过项目、但部署和鉴权踩了坑的人。不管你是哪一类,下面这些内容都是我实际敲过代码、跑过构建、上线之后又踩过坑才总结出来的,可以直接照着做,也可以当排错手册查。

2. create-t3-app 初始化:命令行背后的取舍逻辑

2.1 环境准备与初始化命令

我用的是 pnpm,没选 npm 和 yarn。原因很简单:pnpm 对 monorepo 和依赖提升的处理更干净,安装速度也快,而且 create-t3-app 官方文档默认推荐 pnpm。Node.js 版本我建议至少 18.17 以上,太老的版本跑 Next.js 13/14 会遇到各种摸不着头脑的报错,比如error: unknown option --experimental-loader,浪费半小时发现是 Node 版本问题,血压直接拉满。

新建项目的命令只有一行:

pnpm create t3-app@latest t3code

执行之后交互式命令行会问你项目类型、是否使用 TypeScript、Tailwind CSS、tRPC、Prisma、NextAuth.js 等。我的建议是:除了 ESLint 和 Prettier 这类常规工具外,其余核心选项第一次尽量全选。很多人看到一堆选项就慌,觉得选多了项目复杂。但 T3 Stack 的价值恰恰在于先给你一套完整集成的骨架,然后你再去删,而不是从零拼积木。跳过 Prisma 直接用 ORM?可以,但类型安全主链路就断了一截。跳过 NextAuth?可以,但后面想加登录时又要自己接线。第一次做这个项目,老老实实全选,先把全链路跑通,再考虑精简。

初始化完成后,直接进入项目目录,把开发服务器跑起来:

cd t3code pnpm dev

默认会在 3000 端口起一个 Next.js 应用,首页是 create-t3-app 的欢迎页,里面带了一个 tRPC 健康检查的示例调用。看到 "Hello from tRPC" 字样,说明脚手架没问题。

2.2 为什么这个组合能让类型"贯穿始终"

很多人把 T3 理解成"几个工具碰巧都是 T 开头",其实不对。T3 的名称确实有好几个 T,但它真正的灵魂是"端到端类型安全"(End-to-end Type Safety)这个理念。

拆开看每个角色:

  • Next.js:负责页面渲染和路由,架构上把前端和后端的代码放在同一个代码仓库,甚至同一套路由体系里。它提供了route handlers和 React Server Components,是 T3 的骨架。
  • TypeScript:全项目的语言基础,前后端共享一套类型系统。
  • tRPC:最核心的一环。它让你不需要写 REST API、不需要生成 OpenAPI 文档,直接在服务端定义函数,前端像调用本地函数一样调用服务端函数,并且函数入参和返回值的类型是自动推导的。
  • Prisma:数据库 ORM,从schema.prisma里的数据模型自动生成类型安全的查询客户端。
  • Tailwind CSS:负责样式,属于"工具链里的门面",和类型安全关系不大,但能让你快速做出干净界面。
  • NextAuth.js:处理登录认证,把session注入到 tRPC 的上下文中,让路由里的判断也能享受类型安全。

这套组合里最容易被忽略的是tRPC 带来的心智模型转变。过去我们写前后端分离,前端调用/api/user/list,拿到 JSON 后还要interface User { ... }手写一次类型。tRPC 的做法是:你在服务端定义getUserList函数,前端直接api.user.getUserList.useQuery(),返回的数据类型就是服务端函数的return类型。如果哪天后端把age: number改成了age: string,前端代码里所有用age.toFixed()的地方直接编译报错,根本等不到联调。

有人问:这不就是 GraphQL 吗?不完全一样。GraphQL 需要定义 schema、写 resolver、维护查询语句,中间还是有一层"字符串协议"。tRPC 更极端——它连协议层都用 TypeScript 的函数签名替代了,前后端共享一个类型定义文件。小型和中型团队搞内部系统,这种方式效率非常高。

2.3 生成后的目录结构值得逐层看

create-t3-app 生成的目录结构其实很克制,我理一下核心部分:

t3code/ ├── prisma/ │ ├── schema.prisma # 数据库模型定义 │ └── seed.ts # 种子数据脚本 ├── src/ │ ├── pages/ # Next.js 页面路由 │ │ ├── api/ │ │ │ ├── auth/[...nextauth].ts # NextAuth 入口 │ │ │ └── trpc/[trpc].ts # tRPC HTTP 入口 │ │ └── index.tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── routers/ # tRPC 子路由 │ │ │ └── root.ts # 根路由聚合 │ │ ├── auth.ts # NextAuth 配置 │ │ ├── db.ts # Prisma Client 实例 │ │ └── trpc.ts # tRPC context/初始化 │ ├── styles/ │ └── utils/ │ └── api.ts # 前端 tRPC 客户端 └── .env # 环境变量

看到src/server这个目录了吗?这是 T3 的一个关键约定:服务端相关的代码统一放在server下,前端代码放在pages或app下,两者靠 tRPC 连接。这种做法不会禁止你在 Next.js 里写 API Route,但只要你遵循这个结构,项目的组织方式天然清晰。

utils/api.ts里通常这样初始化前端 tRPC 客户端(新版 create-t3-app 默认走 App Router 配置,但核心逻辑一致):

import { httpBatchLink } from "@trpc/client"; import { createTRPCNext } from "@trpc/next"; import type { AppRouter } from "~/server/api/root"; export const api = createTRPCNext<AppRouter>({ config() { return { links: [ httpBatchLink({ url: "/api/trpc", }), ], }; }, });

这里有个很容易被忽略的细节:httpBatchLink默认会把多个 tRPC 请求合并成一个 HTTP 请求。假如页面里同时调用了用户信息和项目列表,httpBatchLink会把他们打包发出,减少网络往返。但后面我会讲,批处理在某些场景下也会带来诡异的 bug,部署时值得留意。

3. 数据链路贯通:Prisma 建模、tRPC 路由、前端调用

3.1 先建模:Prisma Schema 是整条链路的源头

t3code 我设计的业务很简单:用户(User)、项目(Project)、任务(Task)。一用户可建多个项目,一项目下挂多个任务。这个模型足够覆盖常见 CRUD,又能展示外键关系和鉴权过滤。

prisma/schema.prisma核心模型如下:

model User { id String @id @default(cuid()) name String? email String? @unique projects Project[] tasks Task[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Project { id String @id @default(cuid()) name String ownerId String owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) tasks Task[] createdAt DateTime @default(now()) } model Task { id String @id @default(cuid()) title String done Boolean @default(false) projectId String project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) assigneeId String? assignee User? @relation(fields: [assigneeId], references: [id]) createdAt DateTime @default(now()) }

这里有几个值得说的点。

为什么 ID 用 cuid 而不是自增 Int?这是 Prisma 社区非常推荐的做法。自增 ID 容易暴露业务数据量(比如注册用户数),而且分布式环境下生成有冲突风险。cuid()由 Prisma 客户端生成,无序、无需数据库序列,前端创建记录时甚至可以先拿 ID 后落库,做乐观更新很方便。

外键的onDelete: Cascade要注意使用场景。用户删除时级联删掉他的项目、项目删除时级联删掉任务,这个逻辑没问题。但如果你在真实系统里删用户只是做"软删除"(加deletedAt字段),这里就要慎用级联,不然会误删业务数据。t3code 是演示项目,用级联能少写不少清理代码。

写完 schema 后,执行:

pnpm prisma migrate dev --name init

这条命令会做三件事:创建 SQL 迁移文件、执行迁移到本地数据库、重新生成 Prisma Client。本地数据库我在.env里用的 SQLite(create-t3-app 默认),零配置跑起来最省心:

DATABASE_URL="file:./db.sqlite"

等要部署到生产环境时再切换成 PostgreSQL。这里先提醒一句:换数据库连接串之后,prisma generate必须重新执行,否则客户端类型和实际数据库可能对不上。

3.2 tRPC Router:把数据库能力暴露成类型安全接口

模型建好,接下来是 tRPC 路由。先看src/server/trpc.ts,这里是所有 procedure 的基座:

import { initTRPC, TRPCError } from "@trpc/server"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import { getServerAuthSession } from "~/server/auth"; import { db } from "~/server/db"; export const createTRPCContext = async (opts: CreateNextContextOptions) => { const session = await getServerAuthSession(opts); return { db, session }; }; const t = initTRPC.context<typeof createTRPCContext>().create(); export const createTRPCRouter = t.router; export const publicProcedure = t.procedure; export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.session || !ctx.session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { session: ctx.session } }); });

这个片段值得慢慢拆解。

createTRPCContext是在每次 tRPC 请求进入时执行的函数。它拿到 NextAuth 的 session(如果用户已登录),同时也挂上 Prisma 的db实例。这样所有 tRPC 路由都天然能访问数据库和当前用户。

protectedProcedure是一个自定义的"鉴权包装器"。它先判断 session 是否存在,不存在直接抛UNAUTHORIZED错误,前端 React Query 会把这个错误解析成401状态;存在则把 session 信息继续传给下游处理器。这就是 T3 项目里"登录后才能访问的接口"的标准写法。

然后我在src/server/api/routers/project.ts定义业务路由:

import { z } from "zod"; import { createTRPCRouter, protectedProcedure } from "~/server/trpc"; export const projectRouter = createTRPCRouter({ list: protectedProcedure.query(({ ctx }) => { return ctx.db.project.findMany({ where: { ownerId: ctx.session.user.id }, include: { tasks: true }, orderBy: { createdAt: "desc" }, }); }), create: protectedProcedure .input(z.object({ name: z.string().min(1).max(50) })) .mutation(({ ctx, input }) => { return ctx.db.project.create({ data: { name: input.name, ownerId: ctx.session.user.id, }, }); }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const project = await ctx.db.project.findUnique({ where: { id: input.id }, }); if (!project || project.ownerId !== ctx.session.user.id) { throw new TRPCError({ code: "FORBIDDEN" }); } return ctx.db.project.delete({ where: { id: input.id } }); }), });

注意list查询里用了where: { ownerId: ctx.session.user.id }——用户只能看到自己的项目。delete里更是先查出项目归属再删除,这就是数据级权限控制,必须在服务端做死,前端按钮隐藏只是用户体验层面的辅助。很多人写全栈项目时会把权限校验放在前端路由守卫里,然后后端接口裸奔,这是非常危险的习惯。

输入校验用的是zod,和 tRPC 是官方搭档。zod的作用不只是运行时校验,它还会从 schema 自动推导出 TypeScript 类型,前端在调用project.createmutation 时,input的字段名、类型、必填性全都一目了然,字符串长度不够就会被 tRPC 在服务端直接拒绝。

最后在root.ts里把子路由聚合到根路由:

import { createTRPCRouter } from "~/server/trpc"; import { projectRouter } from "~/server/api/routers/project"; import { taskRouter } from "~/server/api/routers/task"; export const appRouter = createTRPCRouter({ project: projectRouter, task: taskRouter, }); export type AppRouter = typeof appRouter;

AppRouter这个类型被导出后,前端客户端utils/api.ts里的createTRPCNext<AppRouter>()就拿到了后端全部路由的类型信息。从这一刻起,前后端类型就"焊死"了。

3.3 前端调用:像调本地函数一样调服务端逻辑

接下来看前端组件里怎么用。在pages/dashboard.tsx(或 App Router 下的app/dashboard/page.tsx)里:

import { api } from "~/utils/api"; export default function Dashboard() { const { data: projects, isLoading } = api.project.list.useQuery(); const createProject = api.project.create.useMutation({ onSuccess: () => { // 使列表查询失效,自动触发重新获取 void utils.project.list.invalidate(); }, }); if (isLoading) return <div>加载中...</div>; return ( <div> <button onClick={() => createProject.mutate({ name: "新项目" })}> 新建项目 </button> {projects?.map((p) => ( <div key={p.id}> <h2>{p.name}</h2> <span>{p.tasks.length} 个任务</span> </div> ))} </div> ); }

这里有一个初学者容易懵的点:api.project.list.useQuery里返回的projects数据类型,就是我在服务端projectRouter.list里findMany的返回值。如果我改成select: { id: true, name: true }只查两个字段,前端p.tasks.length立刻编译报错——这种"出错"让人无比安心。

useMutation的onSuccess里调用utils.project.list.invalidate(),意思是告诉 React Query:列表数据已经过期,请重新拉取。这是 tRPC + React Query 组合最常见的更新模式,比手动管理全局状态优雅得多。如果你用的还是 Redux 那套"请求成功后 dispatch action 更新 store",换成这套组合后会明显感觉到状态管理负担骤降。

3.4 复杂一点的场景:跨表关联和统计查询

实际业务肯定不止简单的 CRUD。我在 t3code 里还写了一个统计接口,返回用户的任务完成率:

stats: protectedProcedure.query(async ({ ctx }) => { const tasks = await ctx.db.task.findMany({ where: { project: { ownerId: ctx.session.user.id }, }, }); const total = tasks.length; const done = tasks.filter((t) => t.done).length; const rate = total === 0 ? 0 : Math.round((done / total) * 100); return { total, done, rate }; }),

这里用到了 Prisma 的关系过滤:查询条件是project.ownerId = 当前用户,Prisma 会自动 join 子查询。如果用裸 SQL,你得手写 JOIN;用 Prisma,关系字段直接嵌套在 where 里,逻辑清晰,类型也不会丢。这也是为什么我强调 Prisma 是类型安全链路的源头——它在查询层就已经把表的关联关系建模成了 TypeScript 对象。

4. 身份认证:NextAuth 与 tRPC context 的同频

4.1 配置 Provider 与选择登录方案

T3 项目的鉴权默认接入 NextAuth.js,create-t3-app 已经帮你在src/pages/api/auth/[...nextauth].ts和src/server/auth.ts里搭好了壳子。你需要做的就是填 Provider。

我实际配的是 GitHub Provider 和 Credentials(邮箱密码)双通道。GitHub 登录适合演示和内部系统,但正式业务场景大家都更需要手机号 + 验证码或邮箱 + 密码。Credentials Provider 需要你自己写校验逻辑,这里给一个简化版:

import NextAuth from "next-auth"; import GitHub from "next-auth/providers/github"; import Credentials from "next-auth/providers/credentials"; import { db } from "~/server/db"; export const authOptions = { providers: [ GitHub({ clientId: process.env.GITHUB_CLIENT_ID, clientSecret: process.env.GITHUB_CLIENT_SECRET }), Credentials({ credentials: { email: { label: "邮箱" }, password: { label: "密码" }, }, async authorize(credentials) { const user = await db.user.findUnique({ where: { email: credentials?.email }, }); // 生产环境密码绝不能明文存储,务必使用 bcrypt 等哈希算法 if (user && user.password === credentials?.password) { return { id: user.id, name: user.name, email: user.email }; } return null; }, }), ], }; export default NextAuth(authOptions);

这里有个必须强调的警示:示例我故意写了user.password === credentials?.password这种明文比较,实际项目绝对不要这么干。正确做法是bcrypt.compare(credentials.password, user.passwordHash)。Prisma schema 里也应该存passwordHash字段而非password。我在这里省略 hash 逻辑是为了让演示代码聚焦 NextAuth 流程本身,你真正落地时一定要补上。

4.2 session 如何进入 tRPC

在src/server/auth.ts中,create-t3-app 已经封装了一个getServerAuthSession:

export const getServerAuthSession = (ctx: { req: NextApiRequest; res: NextApiResponse; }) => { return getServerSession(ctx.req, ctx.res, authOptions); };

这个函数就是连接 NextAuth 和 tRPC 的桥梁。回想前面src/server/trpc.ts里的createTRPCContext:

const session = await getServerAuthSession(opts); return { db, session };

每次 tRPC 请求进入时,都会先解析当前用户的 session,然后放进 context。所谓 context,你可以理解成"所有路由处理函数都能访问的全局参数对象"。t3code 里,protectedProcedure检查ctx.session,业务路由里ctx.session.user.id拿当前登录用户 ID,靠的就是这套注入机制。

这里的底层逻辑值得说透:NextAuth 默认会在浏览器端种一个 session cookie,前端useSession()能拿到登录状态;tRPC 请求发出时,Next.js 会把包含 session cookie 的请求交给服务端处理,getServerSession从 cookie 里解析出用户信息,放进 tRPC context。全程不需要前端手动传 token,也不需要后端 JWT 解密逻辑,框架帮你处理好了。

4.3 前端如何感知登录状态

在页面组件里:

import { useSession, signIn, signOut } from "next-auth/react"; export default function Header() { const { data: session } = useSession(); return ( <header> {session?.user ? ( <button onClick={() => signOut()}>退出登录</button> ) : ( <button onClick={() => signIn("github")}>GitHub 登录</button> )} </header> ); }

注意一个容易混乱的地方:useSession()拿到的是浏览器端 session 状态,它和 tRPCprotectedProcedure内的ctx.session是来自同一个 NextAuth 系统的。也就是说,前端显示的"已登录"和后端鉴权用的 session 是同源的,不会出现前端已登录、后端却说未认证的错位情况。唯一可能让它们不同步的场景是 session 过期:useSession的 session 还在内存里,但 cookie 已失效,此时调用 tRPC 受保护路由会抛401。React Query 会把这个错误传递给useQuery的error字段,你需要在前端捕获后引导用户重新登录。

const { data: projects, error } = api.project.list.useQuery(); if (error?.data?.code === "UNAUTHORIZED") { void signIn(); }

这里有个很典型的实战细节:tRPC 错误对象里有一个data.code字段,就是你在服务端throw new TRPCError({ code: "UNAUTHORIZED" })时指定的 code。前端可以根据不同的 code 做不同的处理——401 去登录、403 提示无权限、404 提示资源不存在。这比 REST 风格里"所有错误都返回一个{ message: 'error' }"要清晰得多。

5. 部署上线的三次真实意外与修复

5.1 意外一:Prisma 迁移没跑,数据库表全空

本地开发时pnpm prisma migrate dev会在改动 schema 后自动执行迁移。但部署到生产环境(比如 Vercel)时,构建过程不会自动跑迁移。我第一次把 t3code 部署到 Vercel,前端页面能打开,一调 tRPC 的list接口就报 500,日志里写着PrismaClientInitializationError: table "Project" does not exist。

原因很简单:我用的是 PostgreSQL 云数据库,而构建流程只是pnpm build,它只负责编译 Next.js,不会执行数据库迁移。解决办法是在部署前手动执行:

pnpm prisma migrate deploy

注意用的是migrate deploy而不是migrate dev。deploy是专门给生产环境用的:它只执行未应用的迁移文件,不会去对比 schema 差异,也不会触发交互式确认。如果你误在生产环境跑migrate dev,Prisma 检测到 drift 可能会要求重置数据库,那是灾难级的操作。

5.2 意外二:tRPC 请求 400,页面能开但数据加载不出来

这是我踩过最诡异的一个坑:本地一切正常,部署到线上后,部分用户反映页面偶尔加载不出来,Network 面板里/api/trpc/project.list返回 400。

排查到后面发现是httpBatchLink的请求体过大导致的。前面说过,httpBatchLink会把多个 tRPC 请求合并成一个 HTTP 请求。如果一次页面加载里同时发起十几个查询,合并后的请求体会超长,某些代理服务器(包括边缘 CDN)会拒绝处理体积过大的请求体,返回 400。

T3 社区对这个问题的常见解法是:把httpBatchLink换成httpLink——每个 tRPC 请求独立发送,不合并。合并本意是减少网络往返,但对查询数量多、且通过代理服务器访问的场景,单请求独立反而更稳。

import { httpLink } from "@trpc/client"; links: [ httpLink({ url: "/api/trpc", }), ],

如果还是想保留批处理,也可以给httpBatchLink设置maxURLLength或者在服务端[trpc].ts里配置responseMeta让不同的请求走不同缓存策略。但我的建议是:绝大多数小型应用直接用httpLink就好,网络往返的差异在普通业务下几乎感觉不到,省掉批量请求被拒的麻烦更值。

5.3 意外三:NextAuth Secret 没配,用户被随机登出

另一个高频问题出现得比较隐蔽:用户登录后,过一段时间莫名其妙被登出,刷新页面又正常,但有时又被登出。查到最后,原因是NEXTAUTH_SECRET环境变量没配置。

NextAuth 用这个 secret 来加密 session cookie。本地开发时 create-t3-app 的.env里通常提供了一个默认值,所以开发环境一切正常。但部署到生产环境时,你必须要自己生成一个强随机 secret 并配置:

openssl rand -base64 32

然后把输出配置到 Vercel(或你的服务器)的环境变量里:NEXTAUTH_SECRET=...。如果多个实例共用同一个数据库做 session 存储,所有实例必须用同一个 secret;否则实例 A 签发的 cookie 实例 B 验证不过,用户就被"随机登出"。

另外还要检查NEXTAUTH_URL是否配置正确。线上域名是https://t3code.example.com,就写这个完整地址,不要漏掉 https 前缀,否则 NextAuth 构造回调 URL 时可能生成http://链接,登录后回调失败。

5.4 部署时的其他细节清单

如果上面的坑你都趟过去了,部署差不多就稳了。还有些小细节,一并列出来:

  • Prisma Client 在构建时生成:Vercel 构建流程里要确保postinstall脚本执行了prisma generate。如果忘了,构建能过但运行时 Prisma Client 找不到引擎文件,报错信息通常包含Query engine library not found。
  • 环境变量分组管理:.env只放本地开发变量,生产环境变量在部署平台后台单独配置。千万不要把.env提交进 Git 仓库,尤其是里面还有 GitHub OAuth 的 client secret。
  • PostgreSQL 连接串:如果数据库在公网上,记得开 SSL 并设置连接超时。Prisma 的连接串可以带sslmode=require参数,避免某些云数据库默认拒绝非 SSL 连接。

一点收尾的个人体会

t3code 从脚手架到上线,整个过程给我最大的感受是:T3 Stack 真正改变了全栈开发的协作方式。以前写接口先写文档、联调时两边对字段名的场景,在这里完全消失了。不是我不用写文档,而是类型定义本身就是最可执行的文档——它不会过期,不会撒谎。如果你之前一直在做前后端分离,第一次体验 tRPC 的端到端类型推导时,那种"改一个字段、全链路报错提示一起出现"的感觉会让人上瘾。

最后再分享一个小技巧:初学 T3 时不要贪多,先把publicProcedure和protectedProcedure的区别搞清楚,再试着往 Prisma schema 里加一张自己的业务表,然后从页面里调用它。中途遇到任何类型不匹配的编译错误,先别急着改代码,读一读报错信息里指向的字段——那往往就是你的数据模型没设计好的信号。踩过几次坑之后,你会发现 T3 的报错不是在拦你,而是在帮你提前发现问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 16:12:33

Linux离线安装软件包的3种方案与实战技巧

1. 为什么需要离线安装Linux软件包在Linux系统管理中&#xff0c;软件包安装是最基础也最频繁的操作之一。大多数情况下&#xff0c;我们习惯使用apt、yum这类包管理工具在线安装软件&#xff0c;只需一条简单的命令如sudo apt install make就能自动解决依赖关系并完成安装。但…

作者头像 李华
网站建设 2026/10/28 16:11:56

MATLAB GUI音频FIR去噪滤波器设计:从原理到实时处理

简介&#xff1a;这份资源面向数字信号处理课程学习者与MATLAB初学者&#xff0c;围绕音频FIR去噪滤波器设计展开&#xff0c;解决含噪语音信号的降噪与频谱分析问题。包内共5个文件&#xff0c;包含m源码、fig界面文件、docx设计报告、md说明及license&#xff0c;压缩包约598…

作者头像 李华
网站建设 2026/10/28 16:11:54

OpenClaw Agent架构设计与AI任务自动化解析

1. OpenClaw Agent架构设计解析OpenClaw Agent的核心架构采用了分层设计理念&#xff0c;这种设计模式在工业级AI系统中越来越普遍。整个系统可以分为指令理解层、任务规划层、工具调度层和执行反馈层四个关键模块。每个模块都承担着特定的职责&#xff0c;并通过清晰的接口进行…

作者头像 李华
网站建设 2026/10/28 16:11:04

YOLO26与YOLO11:目标检测技术革新与工业应用

1. YOLO26与YOLO11的技术革新背景上周在调试一个工业质检项目时&#xff0c;我意外发现传统YOLOv5对微小缺陷的漏检率高达23%。这让我开始关注最近在GitHub趋势榜爆火的两个新架构——YOLO26和YOLO11。不同于常规版本迭代&#xff0c;这两个模型在骨干网络设计上做出了颠覆性改…

作者头像 李华
网站建设 2026/10/28 16:11:01

Honeywell EPKS SafeView 配置实战:多屏布局、wdl 文件与窗口管理

简介&#xff1a;这份资源面向Honeywell EPKS分布式控制系统的操作员、工程师及项目实施人员&#xff0c;聚焦SafeView功能的配置与实际应用&#xff0c;帮助解决传统Windows多窗口环境下画面分散、关键信息易被覆盖、难以统一管理的问题。内容依据多份Honeywell标准文档与工程…

作者头像 李华
网站建设 2026/10/28 16:10:48

Spring AI 2.0生产落地实战:RAG优化、Agent容错与HTTP边界治理

1. 这不是又一个“Hello World”式AI Demo&#xff1a;Spring AI 2.0落地的真实水位线在哪里&#xff1f;你点开这个标题&#xff0c;大概率不是想看“如何用Spring Boot启动一个带RestController的空项目”&#xff0c;而是被“RAG知识库”“Agent工具调用”“前后端联调”这几…

作者头像 李华