はじめに
「APIのレスポンス型を手書きしていたら、バックエンドの仕様変更に気づかずランタイムエラーが出た…」「フロントエンドとバックエンドで型定義が二重管理になっていて、どちらが正しいのか分からない…」
そんな経験はありませんか?TypeScriptで型を書いても、それがAPIの実際の仕様と一致している保証はどこにもありません。as でキャストした瞬間、型安全は幻想になってしまいます。
そこで活躍するのが「openapi-typescript」です。OpenAPI(Swagger)スキーマからTypeScriptの型定義を自動生成することで、APIの仕様と型定義を常に同期させられます。しかもランタイムコードを一切生成しない「型だけ」のアプローチなので、バンドルサイズへの影響はゼロ。この記事では、openapi-typescriptの基本から、openapi-fetchと組み合わせた実践的な型安全APIクライアントの構築まで解説します。
openapi-typescriptとは
openapi-typescriptは、OpenAPI 3.0/3.1のスキーマ(YAML/JSON)からTypeScriptの型定義を生成するツールです。GitHubで8,200以上のスターを獲得しているMITライセンスのオープンソースで、2026年7月時点の最新バージョンは7.xです。
従来のコードジェネレーター(クラスやクライアントコードを大量に吐き出すタイプ)と違い、openapi-typescriptが生成するのは純粋な型定義ファイル(.d.ts)だけです。ランタイムコードが不要なので軽量で、巨大なスキーマでも数ミリ秒〜数秒で生成が完了します。
主な特徴
- ランタイムフリー - 生成されるのは型定義のみ。バンドルサイズが1バイトも増えません
- OpenAPI 3.0/3.1に完全対応 - discriminatorなどの高度な機能もサポートしています
- ローカル・リモート両対応 - 手元のYAMLファイルはもちろん、URLを指定してリモートのスキーマから直接生成できます
- 高速 - 大規模なスキーマでもミリ秒単位で型を生成します
- エコシステムが充実 - 型安全fetchクライアントの「openapi-fetch」、TanStack Queryと統合する「openapi-react-query」など、姉妹パッケージと組み合わせて真価を発揮します
インストール
Node.js 20以上が必要です。開発時にしか使わないので -D(devDependencies)でインストールします。
# npm
npm i -D openapi-typescript typescript
# pnpm
pnpm add -D openapi-typescript typescript
# yarn
yarn add -D openapi-typescript typescript
また、tsconfig.json で以下の設定を有効にしておくことが推奨されています。
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
基本的な使い方
1. スキーマから型を生成する
CLIを実行するだけです。ローカルファイルでもURLでも指定できます。
# ローカルのスキーマから生成
npx openapi-typescript ./schema.yaml -o ./src/types/api.d.ts
# リモートのスキーマから生成
npx openapi-typescript https://example.com/openapi.yaml -o ./src/types/api.d.ts
たとえば、次のようなOpenAPIスキーマがあるとします。
# schema.yaml
openapi: 3.1.0
info:
title: Blog API
version: 1.0.0
paths:
/posts/{post_id}:
get:
parameters:
- name: post_id
in: path
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
$ref: "#/components/schemas/Post"
components:
schemas:
Post:
type: object
required: [id, title]
properties:
id:
type: string
title:
type: string
body:
type: string
生成された型は、paths と components という2つのインターフェースからアクセスできます。
import type { paths, components } from "./types/api";
// スキーマ定義から型を取り出す
type Post = components["schemas"]["Post"];
// => { id: string; title: string; body?: string }
// エンドポイントのレスポンス型も取り出せる
type PostResponse =
paths["/posts/{post_id}"]["get"]["responses"]["200"]["content"]["application/json"];
スキーマが変わったら再生成するだけで、型のズレが即座にコンパイルエラーとして検出されます。手書きの型定義とはここが決定的に違います。
2. npm scriptsに組み込む
毎回コマンドを打つのは面倒なので、package.json に登録しておきましょう。
{
"scripts": {
"generate:api": "openapi-typescript ./schema.yaml -o ./src/types/api.d.ts"
}
}
CIでスキーマ変更のたびに自動生成すれば、フロントエンドの型は常に最新のAPI仕様と同期します。
実践的なユースケース
openapi-fetchで完全に型安全なAPIクライアントを作る
型定義を生成しただけでは、実際のfetch呼び出しに型を「手で当てはめる」作業が残ります。そこで姉妹パッケージの openapi-fetch の出番です。わずか6KB(axiosは32KB)の軽量クライアントで、URLパス・パラメータ・リクエストボディ・レスポンスのすべてが自動で型付けされます。
npm i openapi-fetch
// src/lib/api.ts
import createClient from "openapi-fetch";
import type { paths } from "../types/api";
export const client = createClient<paths>({
baseUrl: "https://api.example.com/v1",
});
使い方はネイティブfetchの薄いラッパーなので、学習コストはほぼゼロです。
// GETリクエスト: パスパラメータも型チェックされる
const { data, error } = await client.GET("/posts/{post_id}", {
params: {
path: { post_id: "my-first-post" },
},
});
if (error) {
console.error(error); // エラーレスポンスの型も推論される
} else {
console.log(data.title); // dataはPost型。存在しないプロパティはコンパイルエラー
}
// POSTリクエスト: bodyの型もスキーマから検証される
const { data: created } = await client.POST("/posts", {
body: {
title: "openapi-typescriptを導入しました",
body: "手書きの型定義から解放された話",
},
});
注目してほしいのは、型アサーション(as)やジェネリクスの手動指定が一切登場しないことです。存在しないエンドポイントを指定すればエラー、必須パラメータを忘れればエラー、レスポンスの誤ったプロパティにアクセスすればエラー。「手動の型付けはすべてバグの温床になり得る」という思想のもと、any が入り込む余地を徹底的に排除しています。
Reactで使うなら openapi-react-query
TanStack Query(React Query)ユーザーには、さらに openapi-react-query という統合パッケージも用意されています。
import createFetchClient from "openapi-fetch";
import createClient from "openapi-react-query";
import type { paths } from "./types/api";
const fetchClient = createFetchClient<paths>({
baseUrl: "https://api.example.com/v1",
});
export const $api = createClient(fetchClient);
// コンポーネント内で型安全にデータ取得
function PostDetail({ postId }: { postId: string }) {
const { data, isLoading, error } = $api.useQuery("get", "/posts/{post_id}", {
params: { path: { post_id: postId } },
});
if (isLoading) return <p>読み込み中...</p>;
if (error) return <p>エラーが発生しました</p>;
return <h1>{data?.title}</h1>;
}
キャッシュ管理はTanStack Queryに任せつつ、クエリキーやレスポンス型の管理はスキーマから自動化。スキーマ駆動開発の理想形に近い体験が得られます。
まとめ
openapi-typescriptを使えば、次のようなメリットが得られます。
- OpenAPIスキーマからTypeScript型を自動生成し、型定義の二重管理から解放される
- ランタイムコードを生成しないため、バンドルサイズへの影響がゼロ
- openapi-fetchとの組み合わせで、手動の型付けなしに完全型安全なAPI呼び出しが実現できる
- スキーマ変更が即コンパイルエラーになるため、API仕様の変更に強いフロントエンドを作れる
バックエンドがOpenAPIスキーマを公開しているなら、導入しない理由が見つからないレベルのツールです。まずは手元のスキーマに npx openapi-typescript を実行して、生成される型定義を眺めてみてください。手書きの型定義に戻れなくなるはずです。
より詳しい情報は 公式ドキュメント や GitHubリポジトリ を参照してください。