はじめに
「OpenAPIの仕様書は更新したけど、サーバー実装は直し忘れてる」「フロントエンドの型定義とバックエンドのレスポンスが微妙に食い違う」——APIを複数人で開発していると、こういうズレに一度は悩まされたことがあるのではないでしょうか。
OpenAPIやJSON Schema、Protobufはそれぞれ便利な仕様ですが、同じデータ構造を複数のフォーマットで管理しようとすると、どうしても手作業でのメンテナンスが発生し、どこかで食い違いが生まれてしまいます。TypeSpecは、この「一次情報がいくつも存在する問題」を解決するために作られたAPI定義言語です。
TypeSpecとは
TypeSpec(読み方は「ティースペック」)は、Microsoftが開発しているオープンソースのAPI・データ定義言語です。TypeScriptに似た構文でモデルやAPIの形を一度定義するだけで、OpenAPI 3.0、JSON Schema、Protobuf、さらにはクライアント/サーバーのコードやドキュメントまで、さまざまな成果物を自動生成できます。
主な特徴
- TypeScriptライクな構文 -
model、interface、enumなどTypeScriptに慣れたエンジニアなら直感的に書ける文法になっています - デコレータによる制約定義 -
@minLengthや@routeのようなデコレータでバリデーションルールやHTTPルーティングを表現できます - 複数エミッターへの出力 - 同じ
.tspファイルから、OpenAPI3・JSON Schema・Protobufなど目的に応じた形式を生成できます - エディタ支援 - VS CodeやVisual Studio向けの公式拡張があり、構文ハイライトや補完が効きます
インストール
Node.jsがインストールされている環境であれば、npx経由ですぐに試せます。
# プロジェクトを初期化(テンプレート選択式)
npx tsp init
# グローバルインストールする場合
npm install -g @typespec/compiler
# 特定のエミッターを追加する場合(例: OpenAPI3)
npm install --save-dev @typespec/openapi3
tsp initを実行すると、Empty ProjectやREST APIなどのテンプレートを選ぶプロンプトが表示され、main.tspとtspconfig.yamlが生成されます。
基本的な使い方
まずはシンプルなモデルとAPIエンドポイントを定義してみましょう。TypeSpecはコンパイラを介してファイルシステム上で動くツールのため、以下は実際に手元のmain.tspに書いてtsp compileする想定のコードです。
// main.tsp
import "@typespec/http";
import "@typespec/openapi3";
using TypeSpec.Http;
@service(#{ title: "Pet Store" })
namespace PetStore;
model Pet {
id: int32;
@minLength(1)
@maxLength(100)
name: string;
@minValue(0)
age: int32;
kind: "dog" | "cat" | "fish";
}
@route("/pets")
interface Pets {
@get list(): Pet[];
@get getPet(@path id: int32): Pet;
@post createPet(@body pet: Pet): Pet;
}
このファイルを次のコマンドでコンパイルすると、tsp-output/配下にOpenAPI 3.0形式のJSONが出力されます。
tsp compile main.tsp --emit @typespec/openapi3
nameに付けた@minLengthや@maxLengthは、生成されるOpenAPIのminLength/maxLength制約として反映されます。バリデーションルールをコードとドキュメントの両方に手で書く必要がなくなるのがポイントです。
実践的なユースケース
TypeSpecは「同じ定義から何を出力するか」によって使い方のパターンが変わります。代表的な3つのパターンを見ていきましょう。
REST APIの仕様をOpenAPI3として出力する
チームでAPI設計をレビューしたり、SwaggerUIで動作確認したりする場合、まずOpenAPI仕様書が欲しくなります。TypeSpecなら@typespec/openapi3エミッターを指定するだけで、@routeや@getから自動的にOpenAPIのパス定義が組み立てられます。
// orders.tsp
import "@typespec/http";
import "@typespec/openapi3";
using TypeSpec.Http;
@service(#{ title: "Order API" })
namespace OrderService;
model Order {
orderId: string;
total: float64;
status: "pending" | "shipped" | "delivered";
}
model CreateOrderRequest {
items: string[];
}
@route("/orders")
interface Orders {
@get list(): Order[];
@post create(@body body: CreateOrderRequest): Order;
}
tsp compile orders.tsp --emit @typespec/openapi3
# => tsp-output/@typespec/openapi3/openapi.yaml が生成される
JSON Schemaでデータ検証用のスキーマを生成する
APIの仕様書だけでなく、フロントエンドやバッチ処理でのデータバリデーションにも同じモデルを使いたいケースがあります。@typespec/json-schemaを使えば、modelをJSON Schema 2020-12形式でそのまま出力できます。
// user-schema.tsp
import "@typespec/json-schema";
using TypeSpec.JsonSchema;
@jsonSchema
model User {
@minLength(3)
username: string;
@format("email")
email: string;
@minValue(0)
@maxValue(150)
age?: int32;
}
tsp compile user-schema.tsp --emit @typespec/json-schema
# => tsp-output/@typespec/json-schema/User.yaml が生成される
生成されたJSON Schemaは、Ajvなどの一般的なバリデーションライブラリでそのまま利用できます。
Protobufでgrpcサービスを定義する
マイクロサービス間の通信でgRPCを使っている場合も、TypeSpecの@typespec/protobufエミッターで.proto相当の定義を生成できます。RESTとgRPCが混在するシステムでも、モデル定義を一箇所に集約できるのが利点です。
// notification.tsp
import "@typespec/protobuf";
using TypeSpec.Protobuf;
@package(#{ name: "notification" })
namespace Notification;
@message
model NotificationRequest {
@field(1) userId: string;
@field(2) message: string;
}
@message
model NotificationResponse {
@field(1) success: boolean;
}
interface NotificationService {
send(...NotificationRequest): NotificationResponse;
}
tsp compile notification.tsp --emit @typespec/protobuf
このように、REST/JSON Schema/gRPCといった出力形式の違いを、同じTypeSpecの書き方(モデル定義+デコレータ)で吸収できるのが大きな強みです。
まとめ
TypeSpecは、APIやデータの形を一度TypeScriptライクな構文で定義するだけで、OpenAPI・JSON Schema・Protobufといった複数の成果物を一貫して生成できるツールです。「仕様書とコードがズレる」「複数フォーマットを手作業で同期している」といった悩みを抱えているチームであれば、既存のOpenAPI定義を段階的にTypeSpecへ移行してみる価値は十分にあります。まずはnpx tsp initで小さなプロジェクトを作り、手持ちのAPIをひとつだけ書き換えてみることから始めてみてはいかがでしょうか。