今天來介紹 trpc 中非常重要的功能 input/output validate
,有了 input
跟 output
我們就可以透過 type infer
去定義你的 api input
內容與 output
結果。
input
: 用來驗證 client
端傳送內容。output
: 檢查 procedure return
結果是否正確,這邊有一個小提示是, output
並不是必要選項,除非你是以下的情境則可以考慮使用 :
client
端的格式是否正確。code
可閱讀性,如果 procedure function
邏輯越來越複雜,就可以不用看 code 理解 output 內容是什麼了。以下 demo
是用來驗證 input
的 type
是否是 string
,如果不是就 throw error
,這個一個小知識點,如果在 input
中有發生任何錯誤,將會返回 error
結果到 client
端。
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
const publicProcedure = t.procedure;
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
export type AppRouter = typeof appRouter;
然後加上 output
,如果返回結果不是 string
就拋出錯誤。
export const appRouter = t.router({
hello: publicProcedure
.input((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Input is not a string');
})
.output((value): string => {
if (typeof value === 'string') {
return value;
}
throw new Error('Output is not a string');
})
.query((opts) => {
const { input } = opts;
const input: string
return `hello ${input}`;
}),
});
但你以為只有這樣嗎?其實非也 input
跟 output
還可以結合現在很火的 zod
去幫你 validate
內容喔~我們以昨天的 gretting api
為例,這邊我們新增 input
裡面的屬性是 name
,最後 return name
結果。
export const appRouter = router({
greeting: publicProcedure
.input(z.object({
name: z.string()
}))
.query(({ input }) => `hello ${input.name} `),
});
很有趣的是 trpc
這邊會自動幫你 type infer input type
到你的 query function
中,不用而外再寫 type
了。
之後我們在 client
添加 name
// src/index.ts
export default function Home() {
const { data, isLoading, isError } = api.greeting.useQuery({ name: 'Danny' })
if (isLoading) return 'isLoading'
if (isError) return 'isError'
return (
<>
{data}
</>
);
}
之後我們檢查 network response
這邊有一個很特別的地方是 input
其實就是 query
的用法哈哈,還有就是 trpc
會幫你把 query hash
起來,這樣對於 query
的安全性蠻不錯的,不用擔心 input
內容被發現。
最後我們看畫面,這樣我們就成功 call
到 greeting
了~
備註
: 如果讀者不習慣用 zod
的話也可以用其他 input schema
的工具例如 Yup
等等,如果讀者有需要可以自行到官網查看 範例。
Procedures
除了可以透過 initTRPC.create()
外, Procedures
還可以繼承其他的 Procedures
喔~ 下面就是繼承 publicProcedure
然後去做 authorized 。
export const authorizedProcedure = publicProcedure
.input(z.object({ townName: z.string() }))
// trpc 的 middleware 寫法,寫法跟 express 根本一模ㄧ樣
.use((opts) => {
if (opts.input.townName !== 'Pucklechurch') {
throw new TRPCError({
code: 'FORBIDDEN',
message: "We don't take kindly to out-of-town folk",
});
}
return opts.next();
});
export const appRouter = t.router({
hello: publicProcedure.query(() => {
return {
message: 'hello world',
};
}),
getPosts: authorizedProcedure.query(async (opts) => {
const POSTS = await DBCALL()
return {
POSTS
};
}),
});
這樣我們的 appRoute
就可以跟去不同 procedure
管理 api
是否需要驗證 authorized
。
trpc
提供 merge routes
的 function
,可以先宣告 const mergeRouters = t.mergeRouters
之後到 route/index
透過 mergeRouters
,整合所有 appRouter。
// utils/trpc
import { initTRPC } from '@trpc/server';
export const t = initTRPC.create();
export const publicProcedure = t.procedure;
export const mergeRouters = t.mergeRouters;
// routers/index
import { router, publicProcedure, mergeRouters } from '../trpc';
import { z } from 'zod';
import { userRouter } from './user';
import { postRouter } from './post';
const appRouter = mergeRouters(userRouter, postRouter)
export type AppRouter = typeof appRouter;
// routers/user
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';
export const userRouter = router({
userList: publicProcedure.query(() => {
// [..]
return [];
}),
});
// routers/post
import { router, publicProcedure } from '../trpc';
import { z } from 'zod';
export const postRouter = router({
postCreate: publicProcedure
.input(
z.object({
title: z.string(),
}),
)
.mutation((opts) => {
const { input } = opts;
const input: {
title: string;
}
// [...]
}),
postList: publicProcedure.query(() => {
// ...
return [];
}),
});
今天內容到這邊明天繼續~
相關連結github
: https://github.com/Danny101201/next_demo/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y