使いたい背景
APIを実装する場合、個人的には、Protocol BuffersのようなDSLでAPIを定義して、それを元にコードを生成する方法が一番しっくりくる。 REST APIの場合は、OpenAPI(Swagger)で仕様を作成して、そこからドキュメントなり、コードを生成したりなどできるけど、 正直YAMLを書くのが辛くて、苦痛な作業だと感じることが多い。
OpenAPIの仕様をGUIで作成するツールも存在するので。それを使えば良いという意見もあると思う。 だけど、仕様を書くのにも読むのにも外部のGUIツールが必要になってしまうのは、何か間違ってるように感じる。 仕様はコードと同じようにテキストで書くべきものだと思う。
あるいは、バックエンドAPIから自動生成するようなツールを使う方法もある。 ただし、この方法はバックエンドの言語やフレームワークに依存してしまうし、 自分がフロントエンド担当でバックエンドを作成しない場合などは、この方法を採用しづらい。
フロントでTypeScriptでAPIの型定義を書いていると、YAML書くよりずっと良いAPI仕様書では?と思うこともしばしばあって、 TypeScript風にAPIの仕様を書ければ理想的だと思っていた。
そんな中、この課題にぴったりなTypeSpecの存在を知ったので、試してみることにした。
TypeSpecとは
TypeSpecは、Microsoft製のAPIの設計と文書化のためのDSLで、TypeScript風の構文を使用してAPIの仕様を記述できる。
公式サイト: https://typespec.io/
インストールとセットアップ
TypeSpecをグローバルにインストール
npm install -g @typespec/compiler
バージョンの確認
tsp --version
新規プロジェクトの作成。自動的にmain.tsp
, package.json
, tspconfig.yaml
などのファイルが作成される。
tsp init
依存関係のインストール
tsp install
基本的な使用方法
main.tspファイルを作成する。 以下は公式サイトの例として記載されている内容。
import "@typespec/http";
using TypeSpec.Http;
model Store {
name: string;
address: Address;
}
model Address {
street: string;
city: string;
}
@route("/stores")
interface Stores {
list(@query filter: string): Store[];
read(@path id: Store): Store;
}
プログラミング経験がある人であれば、特に文法を知らなくてもストアの一覧と詳細取得を行うAPIが定義されていることが理解できると思う。 OpenAPIのYAMLだとこの程度でもかなりの行数を書かないといけないので、TypeSpecの方が断然読みやすい。
OpenAPIの生成
以下のパッケージをインストールする。
npm install @typespec/openapi3
TypeSpecファイルをコンパイルして、OpenAPI仕様を生成する。
tsp compile main.tsp --emit @typespec/openapi3
tspconfig.yaml
に設定を記載できる。
上記の--emit
オプションは、以下の記載で毎回コマンドラインで指定する必要がなくなる。
emit:
- "@typespec/openapi3"
--watch
でファイル変更を監視して自動再コンパイルできる
tsp compile main.tsp --emit @typespec/openapi3 --watch
デフォルトだと、tsp-output/@typespec/openapi3
以下にopenapi.yaml
が生成される。
ルートにopenapi.yaml
を生成する場合はtspconfig.yaml
に以下の設定を追加する。
options:
"@typespec/openapi3":
emitter-output-dir: "{output-dir}"
output-file: "openapi.yml"
output-dir: "{project-root}"
文法について
モデル定義
model
キーワードを使用して、APIで仕様するデータモデルを定義することができる。
TypeScript風にデフォルト値やOptionalなプロパティも表現できる。
model User {
id: string;
name: string = "Anonymous";
description?: string;
}
デコレーターを使って、ドキュメントやバリデーション情報を追加することができる。
model User {
@doc("ユーザーID")
@maxLength(50)
username: string;
@doc("ユーザーのメールアドレス")
@pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
email: string;
@doc("ユーザーの年齢(18歳以上)")
@minValue(18)
age: int32;
}
enumを使って、列挙型を定義することもできる。
enum UserRole {
Admin,
Editor,
Viewer
}
model User {
role: UserRole;
}
オペレーション (operation)
REST APIでなんらかのエンドポイントを定義したいときは、op
キーワードを使用する。
例えば、ユーザー情報を取得するGETエンドポイントを定義する場合は、以下のようになる。
op getUser(userId: string): User;
デコレーターを使って、オペレーションの付加情報を追加できる。
@get
@route("/users/{userId}")
op getUser(userId: string): User;
インターフェース (interface)
interface
キーワードを使用してインターフェースを定義できる。
インターフェースを使って、複数のオペレーションをグループ化することができる。
ユーザー情報のCRUD操作をまとめたUsersインターフェースを定義する例。
interface Users {
@get
@route("/users")
listUsers(limit?: int32, role?: string): User[];
@get
@route("/users/{userId}")
getUser(userId: string): User;
@post
@route("/users")
createUser(
user: {
name: string;
email: string;
},
): User;
@patch
@route("/users/{userId}")
updateUser(
userId: string,
user: {
name?: string;
email?: string;
},
): User;
@delete
@route("/users/{userId}")
deleteUser(userId: string): void;
}
まとめ
実際に、API仕様書を書く案件で使ってみたところ、 ほとんどTypeScriptコードを書くのと同じように、API仕様書を作ることができた。 VS Codeのエクステンションを使って、静的型チェックのサポートを受けることもできるので、 文法ミスなどもすぐに気づくことができるのが良かった。
プログラミングには「驚き最小の法則」という考え方があるけど、 TypeSpecは、APIの仕様書を書くという観点で、TypeScriptプログラマーにとっての驚き最小を実現しているように感じた。
今後、TypeSpecがどれくらい普及していくかはわからないけど、 個人的には、今後のプロジェクトで積極的に使っていきたいと思う。
次の記事はこちら。