/
CATEGORY
JavaScript
/
使用 ts-rest 與 Zod 定義 API 合約和進行資料驗證

使用 ts-rest 與 Zod 定義 API 合約和進行資料驗證

MUKI AI Summary

本文介紹如何使用 ts-rest 與 Zod 來建立型別安全的 API 合約及進行資料驗證。 ts-rest 是一個專為 TypeScript 設計的工具,能夠幫助開發者輕鬆定義和管理 API 合約,確保前後端的一致性。 Zod 則是一個資料驗證套件,能在執行階段驗證資料的完整性和正確性。文章提供了一個完整的前後端範例,後端使用 Node.js 搭配 Express 框架,前端使用 React.js,並詳細介紹了如何定義 API 路由和使用 Zod 驗證資料。

文章中描述的開發步驟包括:首先使用 ts-rest 定義 API 合約,確保每個 API 路由都有明確的類型定義;接著使用 Zod 驗證資料結構,確保符合預期格式。後端部分,使用 Express 框架建立 API 路由,並透過 ts-rest 將 API 合約與 Express Server 連接,實現資料處理的邏輯。前端則透過 ts-rest 呼叫後端 API,並展示如何在 React 中實作這些功能。文章最後提供了 CodeSandbox 的程式碼範例,供讀者進一步探索和測...

上一篇文章介紹了 ts-rest 以及與使用 TypeScript Interface 的差別,接下來會介紹,如何使用 ts-rest 建立型別安全的 API 合約,並使用 Zod 進行資料驗證。為了發揮 ts-rest 的最大作用,我會用完整的前後端範例,後端使用 Node.js 搭配 Express 框架,前端使用 React.js。

使用的工具與步驟

請容許我再次碎嘴介紹這兩個工具與他們的特性:

ts-rest

他是一個用於 TypeScript 的工具,可以幫助我們更輕鬆的定義和管理 API 合約,確保前後端的一致性。

這邊反覆提起的「合約」,在軟體開發中扮演關鍵角色,特別是前後端分離的專案架構中,以 ts-rest 為例,合約在此定義 API 的結構,確保資料格式一致。

Zod

Zod 是一個資料驗證的套件,我們用 zod 來定義資料結構,並且在 runtime (執行)階段就能進行驗證,確保資料的完整性和正確性。

預期開發步驟

這篇文章預期的開發步驟為:

1.定義 API 合約

我們會使用 ts-rest 來定義 API 路由,包含 request 以及 respond,並確保每個 API 路由都有明確的類型定義。

2.驗證資料

使用 Zod 定義資料結構,並驗證 request 和 respond 的資料,確保符合預期的格式和規範。

後端使用 ts-rest 定義 API 路由

資料結構

為了方便示範,我將前後端放在同個專案,並用 backend 和 frontend 資料夾做區分

安裝套件

▼ 我們需要安裝 typescript, ts-rest, 以及 Zod 相關套件

npm install express
# @ts-rest/express 是給 Express 框架用的,可以參考 ts-rest 的官方文件看有支援哪些框架套件
npm install @ts-rest/core @ts-rest/express zod cors
npm install --save-dev typescript @types/node @types/express

定義 API 合約與使用 Zod 進行資料驗證

接下來,我們在後端建立 API 合約,並透過 Zod 來進行資料結構的驗證。

▼ 定義 API 合約

import { initContract } from "@ts-rest/core";
import { z } from "zod";

const c = initContract();

const Schema = z.object({
  id: z.string(),
  name: z.string(),
  age: z.number(),
});

const ErrorSchema = z.object({
  message: z.string(),
});

// 定義 API 合約
export const apiContract = c.router({
  getUser: {
    method: "GET",
    path: "/user/:id",
    responses: {
      200: Schema,
      404: ErrorSchema,
    },
  },
});

我定義了一個 API 路徑 /user/:id,它會根據用戶 ID 回傳使用者資料。如果找不到該使用者,則會回傳 404 Error。

將 API 合約連接到 Express Server

使用 createExpressEndpoints 將 API 合約 (apiContract) 連到 Express Server,並讓 Server 處理指定路徑的 API 請求。我們要執行以下步驟:

  1. 定義路由邏輯 ( getUser 的處理函數)
  2. 將這些處理函數與 ts-rest API 合約對應

這樣就能根據 apiContract 定義的 API 路由處理請求。

import { createExpressEndpoints, initServer } from "@ts-rest/express";
import express from "express";
import cors from "cors";
import bodyParser from "body-parser";
import { apiContract } from "./src/contract";

// 模擬資料庫的資料
const users = [
  { id: "1", name: "MUKI", age: 18 },
  { id: "2", name: "Apple", age: 25 },
];

const app = express();
app.use(cors())
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const s = initServer();

// 定義 API handler(包含 API 邏輯處理函式的物件)
// 我們定義了 getUser 的處理邏輯,根據 params.id 從一個模擬的資料庫中尋找使用者。如果找到則回傳 status code = 200 和使用者資料,否則回傳 404 和錯誤訊息。
const handlers = {
  getUser: async ({ params }: { params: { id: string } }) => {
    const user = users.find((u) => u.id === params.id);
    if (!user) {
      return {
        status: 404 as const,
        body: { message: "User not found" },
      };
    } else {
      return { status: 200 as const, body: user };
    }
  },
};

// 連接 API 合約與 API 處理邏輯,並將其綁定到 Express 應用程式中
createExpressEndpoints(apiContract, handlers, app);

// 啟動伺服器
app.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

啟動伺服器

伺服器啟動後,輸入 GET /user/:id 來呼叫 API。

▼ 以 CodeSandbox 為例,啟動後在網址列加入 /user/1,就能取得模擬的使用者資料了。

前端使用 ts-rest 呼叫後端 API

打開 frontend 資料夾,安裝 react 與 @ts-rest/react-query 套件

npm install react @ts-rest/core @ts-rest/react-query

▼ 使用 ts-rest 建立 api.ts 檔案,並匯入後端寫好的 API 合約

import { initClient } from "@ts-rest/core";
// 因為我示範的前後端放在同個專案下,所以可用絕對路徑載入 apiContract
import { apiContract } from "../../backend/src/contract";

export const api = initClient(apiContract, {
  // API 網址
  baseUrl: "https://554vqy-3000.csb.app",
});

▼ 在 App.tsx 呼叫 getUser,這邊只列出必要的程式碼

import { api } from "./api";

const fetchUser = async (id: string) => {
  try {
    const response = await api.getUser({ params: { id } });
    if (response.status === 200) {
      setUser(response.body);
      setError(null);
    }
  } catch (err) {
    console.log(err);
    setError("Failed to fetch user");
    setUser(null);
  }
};

▼ 請參考實際執行畫面

到此,我們就做完了前後端基本的 ts-rest 串接。

CodeSandbox 程式碼範例

我將檔案完整範例放在 CodeSandbox 裡,大家可以打開來參考:https://codesandbox.io/p/github/mukiwu/ts-rest-demo/

不過因為使用 CodeSandbox 做完後端 API url,每次啟動 server 時,api 網址都不一樣,所以可能沒辦法直接測試呼叫 API,但可以將檔案 fork 到你自己的 CodeSandbox 試看看。

小結

這篇文章用簡單的範例介紹如何使用 ts-rest 和 Zod 來定義 API,希望能讓大家了解 ts-rest 的運作原理。下一篇預計會分享一些常見的問題,如果你在使用中有碰到問題也歡迎留言告訴我,也許能放進文章內與大家分享 XXD。

歡迎給我點鼓勵,讓我知道你來過 :)

1
MUKI says:

如果文章有幫助到你,歡迎分享給更多人知道。文章內容皆為 MUKI 本人的原創文章,我會經常更新文章以及修正錯誤內容,因此轉載時建議保留原出處,避免出現版本不一致或已過時的資訊。

本文地址:https://muki.tw/use-ts-rest-zod-api-contract/ 已複製

Subscribe
Notify of
guest

0 則留言
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Copyright © since 2008 MUKI space* / omegaSS theme All Rights Reserved.