メインコンテンツへスキップ
日本語ドキュメント

このページは英語の原文を翻訳したものです。内容に相違がある場合は英語版が優先されます。

型付きクライアント

DaloyJS には、完全な型安全性を保ったまま API を呼び出す方法が 2 つ あります。利用者に合ったほうを使ってください。

1. プロセス内の型付きクライアント(コード生成なし)

同じモノレポ内の TypeScript の利用者(テスト、社内ツール、Next.js のサーバーアクション)向けです:

ts
import { createInProcessClient } from "@daloyjs/core/client";
import { app } from "./app.js";

const client = createInProcessClient(app);

const r = await client.getBookById({ params: { id: "1" } });
//    ^? { status: 200; body: { id: string; title: string } }
//      | { status: 404; body: ProblemJson }

if (r.status === 200) {
  console.log(r.body.title); // string、完全に型付け
}

クライアントは operationId をキーとし、{status, body, headers} の判別共用体を返し、すべてをルートの定義そのものから推論します。ビルドステップはありません。

パスパラメーター

パスパラメーターはセグメント単位で置き換えられるため、:id が :idx の中に一致することはなく、各値はそれぞれのセグメントにパーセントエンコードされます。1.3.7 以降、パラメーターが欠けている、空である、. または .. である場合、生成されたメソッドはリクエストを送る前に TypeError をスローします。URL のパースはドットセグメントを(%2E%2E であっても)解決し、呼び出し先を黙って変えてしまうからです。たとえば DELETE /orgs/:org/members/.. が DELETE /orgs/:org になってしまいます。ワイルドカードのルート(*path、または wildcard という名前になる素の *)は / 区切りの値を受け付け、各セグメントは同じ方法でエンコードされ、チェックされます。

ts
// app.get("/files/*path", { operationId: "getFile", ... })
await client.getFile({ params: { path: "docs/guide.md" } }); // GET /files/docs/guide.md

await client.getBookById({ params: { id: ".." } });
// TypeError: Invalid path parameter "id" for /books/:id: empty and "."/".." segments are not allowed

2. Hey API SDK(言語やリポジトリをまたぐ、ビルド時)

モノレポの外にいる利用者や他の言語の利用者向けには、@hey-api/openapi-ts で完全に型付けされた fetch SDK を生成します。

コード生成のパイプライン
  1. 01ルートapp.route(...)
  2. 02generateOpenAPI@daloyjs/core/openapi
  3. 03openapi.jsonディスク上の OpenAPI 3.1 仕様
  4. 04openapi-tsHey API ジェネレーター
  5. 05型付き SDKsdk.gen.ts · types.gen.ts
  6. 06利用者完全に型付けされた fetch 呼び出し
pnpm gen は一連の処理をすべて実行します。ルートから仕様を書き出し、Hey API がそれを型付きの fetch SDK に変換します。ルートを変更するたびに再実行すれば、クライアントはコントラクトと常に足並みをそろえます。
bash
pnpm add -D @hey-api/openapi-ts prettier
ts
// openapi-ts.config.ts
import { defineConfig } from "@hey-api/openapi-ts";

export default defineConfig({
  input: "./generated/openapi.json",
  output: { path: "./generated/client", postProcess: ["prettier"] },
  plugins: ["@hey-api/client-fetch", "@hey-api/typescript", "@hey-api/sdk"],
});
json
// package.json
"scripts": {
  "gen:openapi": "node scripts/dump-openapi.ts",
  "gen:client":  "openapi-ts",
  "gen":         "pnpm gen:openapi && pnpm gen:client"
}
bash
pnpm gen
# 書き出されるファイル:
#   generated/openapi.json
#   generated/client/{client.gen.ts, sdk.gen.ts, types.gen.ts, index.ts}

生成された SDK を使う

ts
import { client } from "./generated/client/client.gen.js";
import { getBookById } from "./generated/client/sdk.gen.js";

client.setConfig({ baseUrl: "https://api.example.com" });

const { data, error } = await getBookById({ path: { id: "1" } });
if (error) console.error(error);
else if (data) console.log(data.title);

どちらを使うべきか

ユースケース選択
同じリポジトリの TypeScript の呼び出し元(テスト、社内ツール)createInProcessClient
別リポジトリの Web アプリ / React Native のモバイルバンドルHey API SDK
TypeScript 以外の利用者(Python、Swift、Kotlin)OpenAPI ドキュメント + 利用者が好むジェネレーター
サードパーティ向けの公開 SDKHey API SDK を独立したパッケージとして公開

ts-rest から移行する場合

ts-rest は人気のあるコントラクトファーストのライブラリで、アダプターベースのサーバー(Express、Fastify、NestJS、Next.js)と fetch クライアント(initClient)の間でコントラクト(initContract)を共有することで、コード生成なしでエンドツーエンドの TypeScript の型を提供します。そのモデルが気に入っているなら、DaloyJS にもなじみやすいはずです。違いは 2 つあります。

1 つ目に、DaloyJS ではルートの定義そのものがコントラクトであり、同期を保つべき別のコントラクトオブジェクトはありません。上で紹介したプロセス内の createClient は、同じリポジトリの TypeScript の呼び出し元に対して、コード生成なしで型を共有する同じ体験を提供します。

2 つ目に、ts-rest の型安全性は TypeScript 専用で、クライアントがコントラクトをインポートする必要があります。DaloyJS は同じルートから第一級の OpenAPI 3.1 仕様と Hey API SDK を出力するため、型をインポートできない利用者(別のリポジトリ、他の言語、公開 SDK)もカバーできます。ts-rest では OpenAPI はオプションのアドオン(@ts-rest/open-api)です。また DaloyJS は、別のフレームワークの上に載せる型付けのレイヤーではなく、サーバーとランタイムそのものであり、Node、Bun、Deno、Cloudflare、Vercel の間で移植できます。

ts-restDaloyJS
コントラクトの情報源別の initContract オブジェクトルートの定義そのもの
コード生成なしの型付きクライアントあり(initClient、TypeScript のみ)あり(createClient、TypeScript のみ)
言語やリポジトリをまたぐクライアントOpenAPI のアドオン(@ts-rest/open-api)OpenAPI 3.1 + Hey API SDK を標準で提供
サーバーExpress / Fastify / NestJS / Next.js 上のアダプター組み込み、ランタイム間で移植可能
実行時の検証Standard Schema(Zod / Valibot / ArkType)Standard Schema(Zod / Valibot / ArkType / TypeBox)
セキュリティのデフォルト自分で用意ヘッダーとボディの上限はデフォルトで有効。CSRF とレート制限は 1 行で追加

生成結果を検証するために、もっと大きなコントラクトが必要ですか? 最小構成のチュートリアルアプリではなく、大規模なフェイク REST デモ(英語)を負荷テストのケースとして使ってください。

最終更新: