API进化论II:使用tRPC快速创建端到端类型安全API
trpc是facebook开源的一款RPC框架,它通过类型系统来保证API的类型安全,并且支持多种语言。
在第一篇文章中,我们介绍了从REST到GraphQL的演变过程,分析了REST的一些局限性以及GraphQL如何解决这些问题。对于第二篇关于tRPC类型安全的文章,我们继续沿用第一篇的思路,从tRPC的定义、工作原理、优势、局限性等方面来介绍tRPC是如何解决API类型安全问题的。
引言:GraphQL最大的缺点就是需要服务端和客户端同时支持,否则无法使用。且开发、维护成本较高,很难在现有项目中引入。
那有没有一种成本更低,使用更简单,类型更安全的方案呢?答案是肯定的,那就是tRPC。
一、什么是tRPC
tRPC是一款基于TypeScript的RPC框架,它通过TS类型系统来保证API的类型安全,下面是 tRPC 官方的演示视频。
即调用远程接口,就像调用本地函数一样,享受完整的类型安全和自动补全功能。
tRPC的定义和基本概念
tRPC(TypeScript Remote Procedure Call)是一个用于构建端到端类型安全API的框架,它允许你在没有代码生成或GraphQL这样的架构的情况下创建类型安全的API。
tRPC利用TypeScript的类型推断能力,让你能够直接从前端调用后端函数,就像它们是本地函数一样,并且享受完整的类型安全和自动补全功能(视频中可得知)。
tRPC与传统API(REST/GraphQL)的对比
对比项 | 优点 | 缺点 |
---|---|---|
Restful API | - 学习成本低 - 绝大部分开发者的选择 |
- 需要手动编写API文档或使用OpenAPI/Swagger这样的工具来维护文档和类型 - 无类型安全 - 获取不足/获取过多 |
GraphQL | - 类型安全 - 按需获取 - 多编程语言支持 |
- 需要定义Schema或使用代码生成工具 - 学习成本高 - 搭建/维护成本高 |
tRPC | - 类型安全 - 全栈开发体验提升 - 可输出 Restful API |
- 需要服务端和客户端同时支持,但成本较低 - 仅支持 json,不支持二进制文件流 |
与REST API相比,tRPC不需要手动编写API文档或使用OpenAPI/Swagger这样的工具来维护文档和类型。与GraphQL相比,tRPC不需要定义Schema或使用代码生成工具,而是直接通过TypeScript的类型系统来实现端到端的类型安全。
tRPC的核心特性介绍
- 端到端类型安全:tRPC的核心优势在于它提供了从后端到前端的完整类型安全。当你在后端定义一个函数(称为"procedure"),它的参数和返回值类型会自动传播到前端,使得前端代码在调用这些函数时能够获得完整的类型检查和自动补全。
// 后端定义procedure
const router = t.router({
hello: t.procedure.query((
{
input: z.object({
name: z.string(),
})
}
) => {
return {
name: input.name,
};
}),
});
// 前端调用procedure
const { data } = trpc.hello.useQuery({ name: 'John' }); // 自动补全,IDE会提示name属性
console.log(data.name); // 自动补全,IDE会提示name属性
-
开发体验提升:通过提供即时的类型反馈和自动补全,tRPC显著提升了开发体验。当API发生变化时,TypeScript编译器会立即在前端捕获到这些变化,减少了由于API变更导致的错误。
-
减少运行时错误:由于在编译时就能捕获大多数类型错误,tRPC可以大大减少运行时错误,提高应用程序的稳定性。
-
自动完成和类型推断:IDE可以提供基于tRPC API的智能提示和自动完成,大大提高了开发效率。
-
可输出Restful API:tRPC可以输出Restful API,方便与现有系统集成。
-
很适合用作 BFF 层:可以作为BFF层,衔接后端微服务,提供统一的API接口。
二、tRPC的工作原理
tRPC的架构设计
tRPC采用客户端-服务器架构,但与传统的API架构不同的是,它通过TypeScript的类型系统在编译时建立前后端的连接。
整体架构包括三个主要部分:
- 服务器端:定义和实现各种procedure(远程调用的函数)
- 客户端:创建tRPC客户端实例来调用服务器端的procedure
- 共享类型:服务器和客户端之间共享的TypeScript类型
tRPC如何实现端到端类型安全
tRPC的核心魔力在于它巧妙地利用了TypeScript的类型系统。当你在服务器端定义procedure时,tRPC会自动生成该procedure的类型定义,并使这些类型在客户端可用。
具体来说,tRPC使用TypeScript的类型推断和泛型功能,将服务器定义的procedure类型无缝传递给客户端。这一过程不需要额外的代码生成步骤,而是直接通过TypeScript的类型系统完成。
代码示例:定义一个tRPC API并使用
服务器端实现
// server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
// 创建tRPC实例
const t = initTRPC.create();
// 定义procedure构建器
const router = t.router;
const publicProcedure = t.procedure;
// 使用Zod进行输入验证
const userRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
// 在实际应用中,这里可能是从数据库获取用户
return {
id: input.id,
name: '张三',
email: 'zhangsan@example.com'
};
}),
createUser: publicProcedure
.input(z.object({
name: z.string(),
email: z.string().email()
}))
.mutation(async ({ input }) => {
// 创建用户的逻辑
return {
id: 'new-user-id',
...input
};
})
});
// 导出类型,这些类型将在客户端使用
export type AppRouter = typeof userRouter;
// 导出router实例
export default userRouter;
客户端实现
// client.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';
// 创建tRPC客户端
const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
}),
],
});
// 调用API,享受完全类型安全
async function main() {
// 获取用户
const user = await trpc.getUser.query({ id: '1' });
console.log(user.name); // 完全类型安全,IDE提供自动补全
// 创建用户
const newUser = await trpc.createUser.mutate({
name: '李四',
email: 'lisi@example.com'
});
console.log(newUser.id);
}
main().catch(console.error);
这个例子展示了tRPC的核心工作流程:
- 在服务器端定义procedure
- 导出router的类型
- 在客户端导入这些类型并创建tRPC客户端
- 使用完全类型安全的API
三、tRPC的局限性
局限性
只适用于TypeScript生态
tRPC的主要局限在于它只适用于TypeScript生态系统。如果你的项目需要支持多种编程语言的客户端,tRPC可能不是最佳选择。
与现有系统的集成挑战
对于已经有大量REST或GraphQL API的系统,迁移到tRPC可能会面临挑战。尽管tRPC可以与这些API共存,但完全迁移可能需要大量工作。
四、实际应用案例与生态
实际运用
tRPC在现实项目中的应用示例
tRPC在许多现代Web应用中被成功应用,特别是那些使用TypeScript全栈开发的项目。例如:
-
SaaS应用平台:许多SaaS创业公司选择tRPC来快速构建他们的API层,因为它可以减少前后端之间的协调成本。
-
中小型Web应用:对于中小型应用来说,tRPC提供了一种低开销、高效率的API实现方式。
-
内部工具和管理系统:由于内部工具通常不需要支持多种客户端语言,tRPC成为这类应用的理想选择。
生态
常用工具和库
tRPC生态系统正在快速发展,一些常用的工具和库包括:
- @trpc/server:服务器端核心库
- @trpc/client:客户端核心库
- @trpc/react-query:与React Query集成
- @trpc/next:与Next.js集成
- zod:用于输入验证的库(虽然不是tRPC的一部分,但经常一起使用)
与Next.js、React Query等的集成
tRPC与Next.js有着极佳的集成体验,特别是在全栈应用开发中。通过@trpc/next
包,你可以在Next.js项目中轻松设置tRPC,并在API路由中使用它。
与React Query的集成也非常强大,@trpc/react-query
包提供了hooks,使得在React组件中调用tRPC API变得简单而高效。
// 在React组件中使用tRPC
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = trpc.getUser.useQuery({ id: userId });
if (isLoading) return <div>加载中...</div>;
if (error) return <div>出错了: {error.message}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
社区发展状况
tRPC社区正在快速增长,尽管相比GraphQL和REST仍然较小,但其活跃度和增长速度令人印象深刻。社区贡献了大量的教程、插件和工具,使得tRPC的生态系统越来越丰富。
GitHub上活跃的贡献者和不断增长的使用案例表明,tRPC正在成为TypeScript全栈开发中的重要工具。
五、未来展望
tRPC的发展趋势
tRPC仍然是一个相对年轻的框架,但其发展前景非常光明:
- 更广泛的框架集成:预计会有更多与流行框架的官方集成
- 性能优化:随着用户基数增加,性能优化将成为重点
- 更丰富的中间件生态:认证、缓存、速率限制等中间件将更加丰富
- 更好的开发工具:专门为tRPC设计的开发工具和调试工具会越来越多
API设计的未来方向
从更广的角度看,tRPC代表了API设计的一个重要发展方向:
- 类型优先的设计:类型系统不再是附加功能,而是API设计的核心
- 低开销集成:减少前后端集成的仪式感和复杂性
- 编译时安全:尽可能多的错误在编译时而非运行时被捕获
- 开发体验为中心:API设计越来越重视开发者体验
类型安全在API领域的更广泛应用
tRPC的成功可能会影响更广泛的API生态系统:
- 其他语言的类似工具:类似tRPC的工具可能会在其他语言生态中出现
- 混合方案的出现:结合GraphQL的灵活性和tRPC的类型安全的混合解决方案
- IDE集成的加深:IDE将提供更深入的API工具集成
六、总结
tRPC在API进化中的位置
tRPC代表了API设计中的一个重要里程碑,它利用TypeScript的类型系统解决了前后端集成的一个核心痛点:类型安全。虽然它可能不会完全取代REST或GraphQL,但它为特定场景提供了一个极具吸引力的选择。
什么时候应该考虑使用tRPC
tRPC最适合以下场景:
- TypeScript全栈项目:如果你的前后端都使用TypeScript,tRPC能发挥最大价值
- 内部工具和应用:不需要支持多种客户端语言的内部应用
- 快速迭代的项目:需要快速开发和频繁变更API的项目
- 小型到中型团队:团队规模不大,需要减少协调成本的项目
如果你的项目需要支持多种编程语言的客户端,或者已经有大量投资在GraphQL基础设施上,那么tRPC可能不是最佳选择。
下一篇可能探讨的主题预告
在API进化的旅程中,我们已经探讨了REST、GraphQL和tRPC。在下一篇文章中,我们可能会讨论gRPC和微服务架构中的API设计,探索如何在分布式系统中构建高效、可靠的API。