gRPC + TypeScript 的两个痛点
- 类型缺失:gRPC 服务调用返回的数据全部是
any类型,无法知道服务有哪些方法、响应有哪些属性 - Observable 包装:所有 gRPC 响应都是
Observable类型,需要手动转换为 Promise
ts-proto 库可以解决这两个问题——它根据 .proto 文件自动生成带完整类型定义的 TypeScript 接口和客户端代码。
安装依赖
pnpm add ts-proto grpc-tools
bash
配置代码生成命令
在 package.json 中添加生成脚本:
{
"scripts": {
"grpc:generate": "grpc_tools_node_protoc \
--plugin=protoc-gen-ts_proto=./node_modules/.bin/protoc-gen-ts_proto \
--ts_proto_out=./src/generated \
--ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false \
-I ./proto \
./proto/user.proto"
}
}
json
关键参数说明:
| 参数 | 作用 |
|---|---|
--plugin | 指定 ts-proto 插件路径 |
--ts_proto_out | 生成文件的输出目录 |
outputServices=nice-grpc | 生成 NestJS 风格的服务接口 |
outputServices=generic-definitions | 生成通用服务定义 |
useExactTypes=false | 不使用精确类型(提高兼容性) |
-I ./proto | proto 文件的搜索路径 |
如果 proto 文件有跨文件引用,需要添加 --proto_path 参数指定引用路径。
生成的文件结构
执行 pnpm grpc:generate 后,输出目录包含:
src/generated/
├── user.ts # 消息类型定义(Request/Response 接口)
└── user-client.ts # 客户端服务接口(带完整类型)
text
生成的类型示例
// 自动生成的接口定义
export interface FindOneRequest {
username: string
}
export interface FindOneResponse {
id: number
username: string
password: string
code: number
message: string
}
export interface UserService {
findOne(request: FindOneRequest): Promise<FindOneResponse>
create(request: CreateRequest): Promise<CreateResponse>
}
typescript
在网关中使用生成的类型
import { UserService, FindOneRequest, FindOneResponse } from './generated/user'
@Controller('auth')
export class AuthController {
private userService: UserService
constructor(@Inject('USER_SERVICE') private client: ClientGrpc) {}
onModuleInit() {
// 现在有完整的类型提示
this.userService = this.client.getService<UserService>('UserService')
}
@Post('login')
async login(@Body() body: FindOneRequest): Promise<FindOneResponse> {
const user = await this.userService.findOne(body)
// user 的类型是 FindOneResponse,有完整的属性提示
return user
}
}
typescript
优势总结
| 维度 | 手写代码 | ts-proto 生成 |
|---|---|---|
| 类型安全 | 全是 any | 完整的 TypeScript 接口 |
| 开发效率 | 需要手动查 proto 文件 | IDE 自动补全 |
| 维护成本 | proto 变更需手动同步 | 重新生成即可 |
| 出错概率 | 高(容易写错字段名) | 低(编译期检查) |
↑