HTTPリクエストを型安全にする手法とOSS

気付いた頃には全部埋まってた本命のTypeScriptアドベントカレンダーの初日が投稿されないようなので代わりに投稿させてもらうことにしました！

## エンドポイントに文字列を使い続ける限りHTTPリクエストは型安全に出来ない

axios, ky, fetch, request, superagent...
どれを使っても「エンドポイントの指定が文字列」なせいで型安全にならない・・・
という課題にTypeScriptユーザーなら誰もが共感してくれるのではないかなーと思ってます
(TypeScript 4.1 の template string types である程度は改善しそうです)

例えば、axiosで以下のようなリクエストをするとして、

```typescript
const userId = 1
const res = await axios.get<History>(`/user/${userId}/histories`)
console.log(res.data)
```
VSCodeで`res.data`の返り値の型を見ると
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63528/2d9fb2aa-0ffa-d52b-2b48-6098927f026c.png)
`History`なんだなあということがわかります
型が表示されてるので一見問題なさそうですが、

＿人人人人人人人人人＿
＞　**突然の仕様変更**　＜
￣Y^Y^Y^Y^Y^Y^Y^￣

- エンドポイントの`user`が`users`に
- 返り値がオブジェクトから配列に

なんてことはまあよくあるわけです
API叩いてるファイルで`/user/`とかで文字列検索して一つずつ書き換えていくことになります

```typescript
const userId = 1
const res = await axios.get<History[]>(`/users/${userId}/histories`)
console.log(res.data)
```
返り値も配列に変わりました
(これがやりたくて無理やりhistoriesを登場させました・・・)
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63528/32e7ebd8-668b-41c9-57da-d07ddd8b870a.png)
しかし

```typescript
const response = await axios.get<History>(`user/${userId}/histories`)
```
のように`/user/`にマッチしない書き方をしている行があって修正漏れ・・・
なんてことにならないように血眼になってファイルを確認する羽目になるわけです

これはTypeScriptだけでなく、静的型付け言語全てにおいて未解決の問題ではないでしょうか
（TypeScript以外の型付け言語を触ったことがないので想像です・・・）
エンドポイントを**文字列**で指定し続ける限り、HTTPリクエストは型安全にならないのです

## エンドポイントをプロパティで指定出来れば型安全になる
`'/users/1/histories'`という文字列から型を判別させることは出来ないけど、
もし`api().users._userId(1).histories`のようにプロパティを繋いでリクエストできれば型安全に出来そうじゃないですか？
イメージはまさにこんな感じ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63528/282831ff-fdc0-cb0f-ab96-9a6a0c1604b1.gif" width="720" alt="aspida.gif" title="aspida.gif" />

VSCodeの補完が効いてタイポすることも無くなります
仕様変更があっても静的に型エラーを検出できるので安心ですね！
残る問題は型定義をどうやってラクに書くか・・・

## エンドポイントの定義はディレクトリ・ファイル名で管理できるとラク

`api().users._userId(1).histories`の型定義を
`api/users/_userId/histories/index.ts`ファイルに書けたら直感的で楽しそう！

```apis/users/_userId/histories/index.ts
type History = {
  id: number
  date: string
  action: 'walking' | 'sleeping'
}

export type Methods = {
  get: {
    resBody: History[] // GETの返り値はHistoryの配列
  }
}
```
こういうファイルを作ったらエンドポイント型定義がいい感じに自動で作成されて

```typescript
const userId = 1
const res = await api().users._userId(userId).histories.get()
console.log(res.body)
```
こう書けるようになり、
さらにVSCodeで返り値の型を確認すると
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63528/32e7ebd8-668b-41c9-57da-d07ddd8b870a.png)
こうなってくれれば最高ですね！

つまりは`api/users/_userId/histories/index.ts`ファイルからエンドポイント型定義を生成するバッチを作ればいい・・・
けどこれが超めんどい！

## 一番めんどいところをやってくれるCLIをOSSとして公開しました

`api/users/_userId/histories/index.ts`ファイルを作ればオートルーティング的にエンドポイント解決してくれていい感じの型定義ファイルを生成するTypeScript製CLI「aspida(アスピーダ)」を作りました
上記のアプローチで型安全なHTTPリクエストが出来るようになります
[GitHub: aspida日本語ドキュメント](https://github.com/aspida/aspida/tree/master/packages/aspida/docs/ja)

### インストール

HTTPクライアントをaxios/ky/fetchから選ぶ必要があります
今回はaxiosのケースを紹介します

```bash
$ npm install @aspida/axios axios
```

### オートルーティング対象のディレクトリを作る
```bash
$ mkdir api
```

apiディレクトリの直下からオートルーティング対象になります

### 型定義
上記で紹介した内容そのままです

```api/users/_userId/histories/index.ts
type History = {
  id: number
  date: string
  action: 'walking' | 'sleeping'
}

export type Methods = {
  get: {
    resBody: History[] // GETの返り値はHistoryの配列
  }
}
```

### ビルドしてルーティング解決されたTSファイルを生成

```package.json
{
  "scripts": {
    "api:build": "aspida"
  }
}
```
```bash
$ npm run api:build

> api/$api.ts was built successfully.
```

apiディレクトリ直下に`$api.ts`というファイルが自動生成されます

### アプリケーションからHTTPリクエストする

```typescript:index.ts
import aspida from '@aspida/axios'
import api from "./api/$api"
;(async () => {
  const client = api(aspida())
  const userId = 1

  const res = await client.users._userId(userId).histories.get()
  console.log(res.body)

  // メソッドに$を付けるとレスポンス内容のみを返す
  const histories = await client.users._userId(userId).histories.$get()
  console.log(histories)
})()
```

フロント・バック問わずaxiosを使える環境ならどこでも動作します
一度型定義するだけでわざわざラッパーを作らずとも安全にHTTPリクエストが出来ます
baseURLやヘッダーにtokenを設定する方法はドキュメントを読んでください
「これはスゴい！」と思ったらGitHubにスターを押してもらえると非常に嬉しいです！
[GitHub: aspida日本語ドキュメント](https://github.com/aspida/aspida/tree/master/packages/aspida/docs/ja)

## aspidaの意味
ギリシャ語で「盾」です
型安全の世界観と合っていてカッコいいなと思って名付けました！
(ちなみにaxiosはギリシャ語で「価値が有る」らしい)

![logo.png](https://aspida.github.io/aspida/logos/svg/black.svg)

## GitHubのスターを押してもらえると嬉しいです

aspidaは2019年10月から実戦投入しており、ほとんどのユースケースに対応できるレベルに達しています
ユーザーから報告があったバグも数時間以内に修正してリリース対応してきました
日本語英語両方でドキュメント整備も頑張っているのでGitHubのスターを押してもらえると非常に嬉しいです！
[GitHub: aspida日本語ドキュメント](https://github.com/aspida/aspida/tree/master/packages/aspida/docs/ja)