# nest.js tRPC API建置
tRPC 是一個基於 TypeScript 的遠端過程呼叫(RPC)框架,簡化client端與server端之間的通訊,並提供高效率的類型安全性。它允許使用類似本地函數呼叫的方式來呼叫遠端函數,同時自動處理序列化和反序列化、錯誤處理和通訊協定等底層細節。
## 套件安裝
```bash!
npm install nestjs-trpc zod
npm install trpc-panel
```
## 基本資訊
使用tRPC裡有3個構成要素,router、context、middleware,個核心概念有助於建立類型安全且高效的 API。
* **Router**: 用於定義和組織 tRPC 的程式(procedures),類似於傳統的控制器。透過在類別上使用 @Router() 裝飾器,並在方法上使用 @Query() 或 @Mutation() 裝飾器,可以定義查詢或變更操作。
* **Context**: 是在 tRPC 中用於共用請求範圍內的資料的物件。在 nestjs-trpc 中,可以透過實作 TRPCContext 介面來定義內容。允許在程式執行期間存取共享的資料或服務。
* **Middleware**:是在請求處理之前或之後執行的函數,用於添加額外的功能或修改請求和回應。在 nestjs-trpc 中,可以透過實作 TRPCMiddleware 介面來建立中間件
## 建置context
context為定義tRPC的數據,所有傳輸都會經過context的定義再回饋於middleware跟router,首先建置context的ts檔,並且聲明需導入的傳輸項目
==src\dogs\dogs.context.ts==
```typescript!
import { Injectable } from '@nestjs/common';
import { ContextOptions, TRPCContext } from 'nestjs-trpc';
@Injectable()
export class DogsContext implements TRPCContext {
create(opts: ContextOptions): Record<string, unknown> {
return {
isContextApplied: true,
req: opts.req,
res: opts.res,
//設定可以於middleware讀取到req跟res訊息
};
}
}
```
## 建置route
route先廸立最簡單的查詢,定義findAll跟findQuery,findQuery可以帶入input參數
==dogs.router.ts==
```typescript!
import { Router, Query, Input, UseMiddlewares } from 'nestjs-trpc';
import { z } from 'zod';
@Router({ alias: 'trpcs' })
export class DogsRouter {
constructor() { }
@Query()
async findAll() {
return `findAll Hello at ${new Date().toISOString()}`
}
@Query({ input: z.object({ name: z.string() }) })
async findQuery(@Input('name') name: string) {
console.log('name:', name)
return `findQuery Hello ${name} at ${new Date().toISOString()}`
}
}
```
## module設定
module檔案裡需要imports套件TRPCModule才能產生運作tRPC的server.ts
==dogs.module.ts==
```typescript!
import { Module } from '@nestjs/common';
import { DogsRouter } from './dogs.router';
import { TRPCModule } from 'nestjs-trpc';
import { DogsController } from './dogs.controller';
import { DogsContext } from './dogs.context';
@Module({
imports: [
TRPCModule.forRoot({
autoSchemaFile: 'production', //這裡於build時會產生production目錄及server.ts檔
context: DogsContext //這裡要定義運作的Context
}),
],
controllers: [DogsController],
providers: [DogsRouter, DogsContext]
})
export class DogsModule { }
```
## panel使用進行測試
panel為server端產生tRPC測試的頁面
```typescript!
import { Controller, OnModuleInit, Inject, All } from '@nestjs/common';
import { AppRouterHost } from 'nestjs-trpc';
import { renderTrpcPanel } from 'trpc-panel';
import { AnyRouter } from '@trpc/server';
@Controller('dogs')
export class DogsController implements OnModuleInit {
private appRouter: AnyRouter;
constructor(
@Inject(AppRouterHost) private readonly appRouterHost: AppRouterHost,
) { }
onModuleInit() {
this.appRouter = this.appRouterHost.appRouter;
}
@All('panel')
panel(): string {
return renderTrpcPanel(this.appRouter, {
url: 'http://localhost:3000/trpc', //設定自身的連結,trpc為固定router
});
}
}
```
運作後可以直接於瀏覽器http://localhost:3000/dogs/panel查看所產生的介面

## middleware
middleware為濾tRPC傳輸的驗證,通常為像auth的登入驗證
==src\dogs\dogs.middleware.ts==
```typescript!
import {
MiddlewareOptions,
MiddlewareResponse,
TRPCMiddleware,
} from 'nestjs-trpc';
import { Injectable } from '@nestjs/common';
import { TRPCError } from '@trpc/server';
@Injectable()
export class DogsMiddleware implements TRPCMiddleware {
constructor() { }
async use(opts: MiddlewareOptions<{ req: Request }>): Promise<MiddlewareResponse> {
const start = Date.now();
const result = await opts.next();
//測試驗證如果有Error字串會回傳UNAUTHORIZED
if (opts.rawInput?.['name'] === 'Error') {
throw new TRPCError({ message: "No rawInput found.", code: "UNAUTHORIZED" });
}
const durationMs = Date.now() - start;
const meta = { path: opts.path, type: opts.type, durationMs };
result.ok
? console.log('OK request timing:', meta)
: console.error('Non-OK request timing', meta);
return result;
}
}
```
於router裡加入UseMiddlewares驗證
==src\dogs\dogs.router.ts==
```typescript!
...以上省略
@UseMiddlewares(DogsMiddleware)
@Query({ input: z.object({ name: z.string() }) })
async findUser(@Input('name') name: string) {
console.log('name:', name)
return `findQuery Hello ${name} at ${new Date().toISOString()}`
}
```
別忘了module裡需要providers導入Middleware
==src\dogs\dogs.module.ts==
```typescript!
providers: [DogsService, DogsRouter, DogsContext, DogsMiddleware]
})
```
進行測試時帶入字串Error則會res出401錯誤

最後,要將build的"production\server.ts"檔案丟給client端使用,client才能夠讀取server.ts的設定進行傳輸。
## 結尾廢言
tRPC如果用於傳輸協定,其效率會比使用GraphQL來得好,多了安全性及型別驗證功能,就可以避免許多不必要的問題。但如果server跟cleint為分離開發,都要用typescript才能運作,如果是用其他語法會需要更多轉換的方式,代價並不少。
不管是tRPC或GraphQL,最終都是以RESTFUL API來進行傳輸,並不像gRpc用Protocol Buffers傳輸,所以對client端而言,即然都是用REST,為何還要繞一圈來使用,增加彼此的學習成本。
所以,要怎麼說服client端使用,應該會比提供技術的運用,來的更麻煩。
參考:
> https://angularexperts.io/blog/angular-trpc?utm_source=chatgpt.com
> https://www.nestjs-trpc.io/