.agents/skills/openapi-first/SKILL.md
OpenAPI仕様を駆動としてバックエンドとフロントエンドの型安全性を確保する場合に使用する。バックエンドでOpenAPIを生成し、フロントエンドでコード生成して型共有を実現する。
npx skillsauth add ymkz/demo-monorepo openapi-firstInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
バックエンド(Spring Boot)でOpenAPI仕様を生成し、フロントエンド(TypeScript)でコード生成することで、API契約に基づく型安全性を確保する開発手法。
┌─────────────────────────────────────────────────────────────┐
│ OpenAPI First │
├─────────────────────────────────────────────────────────────┤
│ │
│ Backend (Spring Boot) │
│ ┌────────────────────┐ ┌──────────────────┐ │
│ │ @RestController │──────│ springdoc-openapi │ │
│ │ @Schema │ │ (OpenAPI生成) │ │
│ └────────────────────┘ └────────┬─────────┘ │
│ │ openapi.json │
│ ┌─────────────────────────────────────┴─────────────────┐ │
│ │ src/main/resources/static/openapi/ │ │
│ │ openapi.json (Git管理) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────┼──────────────────────────────┘
│
Frontend (Next.js) │
│ │
│ ┌───────────────────────────┴──────────────────────────┐ │
│ │ @hey-api/openapi-ts │ │
│ │ ├── client/ (APIクライアント) │ │
│ │ ├── types/ (TypeScript型) │ │
│ │ └── core/ (共通型) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────┘ │
│ │ fetch('/api/books') │
│ │ ↓ 型安全なAPI呼び出し │
│ └────────────────────────────────────────────────────────────┘
apps/api/build.gradle.kts:
plugins {
id("common-conventions")
alias(libs.plugins.spring.boot)
alias(libs.plugins.springdoc.openapi)
}
dependencies {
implementation(platform(libs.spring.boot.bom))
implementation(libs.springdoc.openapi.starter.webmvc.api)
// ...
}
openApi {
apiDocsUrl.set("http://localhost:8080/openapi/openapi.json")
outputDir.set(rootProject.file("apps/api/src/main/resources/static/openapi"))
outputFileName.set("openapi.json")
}
tasks.named("build") {
dependsOn("generateOpenApiDocs")
}
@RestController
@RequestMapping("/books")
@Tag(name = "Books", description = "書籍管理API")
public class BookController {
@GetMapping
@Operation(summary = "書籍一覧取得")
public ResponseEntity<BookSearchResponse> search(
@Parameter(description = "検索キーワード")
@RequestParam(required = false) String keyword) {
// ...
}
}
@Schema(description = "書籍検索レスポンス")
public record BookSearchResponse(
@Schema(description = "書籍リスト")
List<Book> books,
@Schema(description = "総件数")
long total
) {}
apps/web-form/package.json:
{
"scripts": {
"generate:openapi": "openapi-ts",
"dev": "wireit",
"build": "wireit"
},
"wireit": {
"generate:openapi": {
"command": "openapi-ts",
"files": [
"../../apps/api/src/main/resources/static/openapi/openapi.json"
],
"output": [
"src/generated/**"
]
},
"dev": {
"command": "next dev",
"service": true,
"dependencies": ["generate:openapi"]
}
},
"devDependencies": {
"@hey-api/openapi-ts": "catalog:"
}
}
import { defineConfig } from '@hey-api/openapi-ts';
export default defineConfig({
client: '@hey-api/client-fetch',
input: '../../apps/api/src/main/resources/static/openapi/openapi.json',
output: {
path: './src/generated',
},
types: {
enums: 'typescript',
},
});
src/generated/
├── client/
│ └── services.gen.ts # APIクライアント関数
├── types/
│ └── types.gen.ts # TypeScript型定義
└── core/
└── ... # 共通ユーティリティ
// BookController.java
@PostMapping
@Operation(summary = "書籍登録")
public ResponseEntity<BookResponse> create(
@RequestBody @Valid BookCreateRequest request) {
// 実装
}
# GradleタスクでOpenAPI仕様を生成
./gradlew :apps:api:generateOpenApiDocs
# 出力: apps/api/src/main/resources/static/openapi/openapi.json
# pnpmでTypeScriptコードを生成
cd apps/web-form
pnpm generate:openapi
# 出力: src/generated/{client,types}/
// Next.js App Router (Server Component)
import { getBooks } from '@/generated/client';
export default async function BooksPage() {
// 型安全なAPI呼び出し
const { data: books } = await getBooks({ query: { keyword: 'Spring' } });
return (
<ul>
{books?.map(book => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
# apps/web-form/.gitignore
src/generated/
# Git管理対象
apps/api/src/main/resources/static/openapi/openapi.json # ✅ バックエンドで生成
# Git管理外(.gitignore)
apps/web-form/src/generated/ # ❌ フロントエンドで生成
// ✅ 良い: 明示的なSchemaアノテーション
@Schema(description = "書籍ID", example = "1")
private Long id;
// ❌ 避ける: 暗黙的な型推定
private Long id; // descriptionなし
// ✅ 良い: バリデーションと併用
@Schema(description = "書籍タイトル")
@NotBlank
@Size(max = 100)
private String title;
// ✅ 良い: 生成コードの直接使用
import { getBooks, createBook } from '@/generated/client';
// ❌ 避ける: 手動での型定義
interface Book { // 重複!
id: number;
title: string;
}
| 問題 | 原因 | 解決策 |
|------|------|--------|
| 型が生成されない | OpenAPI仕様の更新 | ./gradlew generateOpenApiDocsを再実行 |
| フロントエンドで型エラー | API変更と同期不足 | バックエンド→フロントエンドの順で生成 |
| 循環参照エラー | スキーマ定義の問題 | @Schemaアノテーションを見直す |
| パス解決エラー | configのパス設定 | openapi-ts.config.tsのinputパスを確認 |
tools
npm/pnpmベースのモノレポでWireitを使用してビルドパイプラインの依存関係を管理し、キャッシュと並列実行を活用する場合に使用する。
tools
このプロジェクトでは1リクエストあたりの全イベントを1つのJSONログに集約する「ワイドイベントロギング」を採用している。MDCとThreadLocalを組み合わせ、リクエスト処理のトレーサビリティ向上とパフォーマンス分析を実現する。
testing
単体テストとインテグレーションテストを使い分ける場合に使用する。テストピラミッドに基づき、テストの責務、実行速度、メンテナンスコストを考慮して適切なテスト戦略を選択する。
development
Spiceflow is a super simple, fast, and type-safe API and React Server Components framework for TypeScript. Works on Node.js, Bun, and Cloudflare Workers. Use this skill whenever working with spiceflow to get the latest docs and API reference.