iT邦幫忙

2023 iThome 鐵人賽

DAY 1
0
Modern Web

一些讓你看來很強的全端- trcp 伴讀系列 第 7

Day-07. 一些讓你看來很強的全端 TRPC 伴讀 - Input / Output Validate

  • 分享至 

  • xImage
  •  

今天來介紹 trpc 中非常重要的功能 input/output validate ,有了 inputoutput 我們就可以透過 type infer 去定義你的 api input 內容與 output 結果。

Input & Output Validators

input : 用來驗證 client 端傳送內容。
output : 檢查 procedure return 結果是否正確,這邊有一個小提示是, output 並不是必要選項,除非你是以下的情境則可以考慮使用 :

  • 確保傳送到 client 端的格式是否正確。
  • 增加 code 可閱讀性,如果 procedure function 邏輯越來越複雜,就可以不用看 code 理解 output 內容是什麼了。
  • 檢查從不信任返回的資料是否正確。

以下 demo 是用來驗證 inputtype 是否是 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}`;
    }),
});

但你以為只有這樣嗎?其實非也 inputoutput 還可以結合現在很火的 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 內容被發現。

最後我們看畫面,這樣我們就成功 callgreeting 了~

備註: 如果讀者不習慣用 zod 的話也可以用其他 input schema 的工具例如 Yup 等等,如果讀者有需要可以自行到官網查看 範例

Define Procedures

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

Merging Routers

trpc 提供 merge routesfunction,可以先宣告 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


上一篇
Day-06. 一些讓你看來很強的全端 TRPC 伴讀 - Define Routers
下一篇
Day-08. 一些讓你看來很強的全端 TRPC 伴讀 - Context / middleware
系列文
一些讓你看來很強的全端- trcp 伴讀30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言