Slide 1

Slide 1 text

TypeSpecで実現する 辛くないOpenAPIスキーマ駆動開発 ふくすけ (@tonegawa07) 2025/08/20 | Mita.ts 1

Slide 2

Slide 2 text

自己紹介 ふくすけ (@tonegawa07) スタークス株式会社 仕事: Engineer Ruby on Rails, TypeScript(Node.js) データ基盤 (BigQuery) 趣味: サッカー観戦 (Jサポ) ひとこと: 社内勉強会やテックブログの運営してます 2025/08/20 | Mita.ts 2

Slide 3

Slide 3 text

スキーマ駆動開発 2025/08/20 | Mita.ts 3

Slide 4

Slide 4 text

スキーマ駆動開発とは スキーマファースト APIの仕様を最初に定義 フロントエンド/バックエンドが並行開発可能 自動化 スキーマから型定義やドキュメントを自動生成 モックサーバー スキーマを基にモックサーバーを構築 API実装前にフロントエンドの開発が可能 2025/08/20 | Mita.ts 4

Slide 5

Slide 5 text

スキーマ駆動開発とは APIスキーマを中心に開発を進める手法 自動生成 API スキーマ バックエンド フロントエンド API ドキュメント 2025/08/20 | Mita.ts 5

Slide 6

Slide 6 text

OpenAPI 2025/08/20 | Mita.ts 6

Slide 7

Slide 7 text

OpenAPIとは REST APIを記述するための標準フォーマット openapi: 3.0.0 paths: /users: get: summary: ユーザー一覧取得 responses: '200': content: application/json: schema: type: array items: $ref: '#/components/schemas/User' 2025/08/20 | Mita.ts 7

Slide 8

Slide 8 text

OpenAPIのメリット 豊富なエコシステム コード生成ツール openapi-generator, openapi-typescript ドキュメント生成 Swagger UI, Redoc モックサーバー Prism, Mockoon 2025/08/20 | Mita.ts 8

Slide 9

Slide 9 text

TypeScriptでのスキーマ駆動開発 2025/08/20 | Mita.ts 9

Slide 10

Slide 10 text

技術スタック バックエンド TypeScript Express openapi-typescript - OpenAPIから型定義を生成 フロントエンド TypeScript React openapi-typescript - OpenAPIから型定義を生成 openapi-fetch - 型安全なAPIクライアント 2025/08/20 | Mita.ts 10

Slide 11

Slide 11 text

使用ライブラリ openapi-typescript https://openapi-ts.dev/ OpenAPIスキーマからTypeScriptの型定義を自動生成 openapi-fetch https://openapi-ts.dev/openapi-fetch/ 型安全なfetchクライアント(openapi-typescriptと連携) 2025/08/20 | Mita.ts 11

Slide 12

Slide 12 text

openapi-typescriptで型生成 npx openapi-typescript ./path/to/my/schema.yaml -o ./path/to/my/schema.d.ts import type { paths, components } from "./my-openapi-3-schema"; // Schema Obj type MyType = components["schemas"]["MyType"]; // Path params type EndpointParams = paths["/my/endpoint"]["parameters"]; // Response obj type SuccessResponse = paths["/my/endpoint"]["get"]["responses"][200]["content"]["application/json"]["schema"]; type ErrorResponse = paths["/my/endpoint"]["get"]["responses"][500]["content"]["application/json"]["schema"]; https://openapi-ts.dev/introduction 2025/08/20 | Mita.ts 12

Slide 13

Slide 13 text

Express Request型の拡張 型安全なリクエストハンドラー import type { Request, Response } from "express"; import type { paths } from "./my-openapi-3-schema"; // OpenAPI のpaths から直接型を取得 type UpdateUserBody = paths["/users/{id}"]["patch"]["requestBody"]["content"]["application/json"]; type UpdateUserPath = paths["/users/{id}"]["patch"]["parameters"]["path"]; type UpdateUserQuery = paths["/users/{id}"]["patch"]["parameters"]["query"]; // Express Request 型に適用 app.patch("/users/:id", async ( req: Request, res: Response ) => { const { id } = req.params; // 型安全! const { name } = req.body; // 型安全! const { sort } = req.query; // 型安全! }); 2025/08/20 | Mita.ts 13

Slide 14

Slide 14 text

openapi-fetchで型安全なAPI呼び出し (GET) import createClient from "openapi-fetch"; import type { paths } from "./my-openapi-3-schema"; const client = createClient({ baseUrl: "https://myapi.dev/v1/" }); const { data, // only present if 2XX response error, // only present if 4XX or 5XX response } = await client.GET("/blogposts/{post_id}", { params: { path: { post_id: "123" }, }, }); https://openapi-ts.dev/openapi-fetch/ 2025/08/20 | Mita.ts 14

Slide 15

Slide 15 text

openapi-fetchで型安全なAPI呼び出し (PUT) import createClient from "openapi-fetch"; import type { paths } from "./my-openapi-3-schema"; const client = createClient({ baseUrl: "https://myapi.dev/v1/" }); await client.PUT("/blogposts", { body: { title: "My New Post", }, }); https://openapi-ts.dev/openapi-fetch/ 2025/08/20 | Mita.ts 15

Slide 16

Slide 16 text

openapi.yamlのつらみ 2025/08/20 | Mita.ts 16

Slide 17

Slide 17 text

openapi.yamlの記述例 components: schemas: User: type: object required: - id - email properties: id: type: string format: uuid email: type: string format: email 2025/08/20 | Mita.ts 17

Slide 18

Slide 18 text

openapi.yamlの課題 ファイルの肥大化 中規模APIでも数千行 (全体像の把握が困難) ファイル分割の複雑さ $refによる参照関係が煩雑 分割ルールの統一が困難 可読性・保守性の問題 深いネスト構造 YAMLインデントの管理 2025/08/20 | Mita.ts 18

Slide 19

Slide 19 text

TypeSpec 2025/08/20 | Mita.ts 19

Slide 20

Slide 20 text

TypeSpecとは API-First for developers With TypeSpec, remove the handwritten files that slow you down, and generate standards-compliant API schemas in seconds. https://typespec.io/ TypeScriptライクな構文で、既存の知識をそのまま活用可能 TypeSpecから様々な形式に出力、それぞれのエコシステムと統合 マルチプロトコルサポート OpenAPI, JSON Schema, Protobuf 2025/08/20 | Mita.ts 20

Slide 21

Slide 21 text

TypeSpecの良いところ API仕様をTypeScirpt風に記述できるDSL 標準フォーマットであるOpenAPIやJSON Schemaを生成しているに過ぎない 依存度が低く、ライブラリへの過度なロックインを避けられる 2025/08/20 | Mita.ts 21

Slide 22

Slide 22 text

TypeSpecからOpenAPIへ TypeSpecファイル (.tspファイル) からopenapi.yamlを生成 # TypeSpec ファイル (main.tsp) をコンパイル tsp compile . # openapi.yaml が生成される openapi.yamlの直接編集から解放 2025/08/20 | Mita.ts 22

Slide 23

Slide 23 text

TypeSpecのセットアップ # TypeSpec のインストール npm install -g @typespec/compiler # プロジェクトの初期化 mkdir my-typespec-project cd my-typespec-project tsp init # コンパイル tsp compile . # src/main.tsp をコンパイルして dist ディレクトリにopenapi.yaml を生成 tsp compile ./src --output-dir ./dist https://typespec.io/docs/ 2025/08/20 | Mita.ts 23

Slide 24

Slide 24 text

import "@typespec/http"; using Http; model User { id: string; email: string; address?: Address; } model Address { street: string; city: string; } @route("/users") interface Users { list(@query filter?: string): User[]; create(@body user: User): User; read(@path id: string): User; } 2025/08/20 | Mita.ts 24

Slide 25

Slide 25 text

モデルとルートの分割 ファイル分割で可読性向上 src/ ├── main.tsp # エントリーポイント ├── models/ # データモデル定義 │ └── user.tsp └── routes/ # API ルート定義 └── users.tsp メリット 責務の分離が明確 可読性の向上 2025/08/20 | Mita.ts 25

Slide 26

Slide 26 text

分割例: モデル定義 models/user.tsp model User { id: string; email: string; address?: Address; } model Address { street: string; city: string; } 2025/08/20 | Mita.ts 26

Slide 27

Slide 27 text

分割例: ルート定義 routes/users.tsp import "@typespec/http"; import "../models/user.tsp"; using Http; @route("/users") interface Users { list(@query filter?: string): User[]; create(@body user: User): User; read(@path id: string): User; } 2025/08/20 | Mita.ts 27

Slide 28

Slide 28 text

分割例: エントリーポイント main.tsp import "@typespec/http"; // モデルのインポート import "./models/user.tsp"; // ルートのインポート import "./routes/users.tsp"; 2025/08/20 | Mita.ts 28

Slide 29

Slide 29 text

CIでopenapi.yaml自動生成 GitHub Actionsによる自動化フロー CI 処理 変更あり 変更なし 差分あり 差分なし PR 作成/ 更新 TypeSpec 変更チェック コンパイル実行 OpenAPI 差分チェック 自動コミット PR に反映 終了 2025/08/20 | Mita.ts 29

Slide 30

Slide 30 text

開発フロー TypeSpecを用いたスキーマ駆動開発 API 設計 TypeSpec でAPI 仕様を定義 openapi.yaml 生成 openapi-typescript で型生成 バックエンド(Express Reques フロントエンド(openapi-fetc 2025/08/20 | Mita.ts 30

Slide 31

Slide 31 text

まとめ 開発効率の向上 TypeScript風のDSLでAPI仕様を記述 openapi.yamlの直接編集から解放 保守性の向上 ファイル分割で可読性アップ モデルとルートの責務分離 自動化による品質向上 CIでOpenAPI自動生成 型定義の自動生成で型安全を実現 既存エコシステムとの統合 OpenAPIの豊富なツール群をそのまま 活用 openapi-typescript/openapi-fetchとの 連携 2025/08/20 | Mita.ts 31

Slide 32

Slide 32 text

TypeSpecで 辛くないOpenAPIスキーマ駆動開発を始めよう! 2025/08/20 | Mita.ts 32

Slide 33

Slide 33 text

ご清聴ありがとうございました 2025/08/20 | Mita.ts 33