はじめに

aspida は API のリクエスト/レスポンスに型定義ができる HTTP クライアントラッパーです。 API から取得する内容に予め型を定義しておくことで型安全に API を利用することを目的にしています。

インストール

yarn add @aspida/fetch

@aspida/fetch のほかにも、@aspida/axios / @aspida/ky / @aspida/node-fetch が用意されています。

microCMS でAPIスキーマを設定

microCMS でテスト用のデータを作成しましょう、今回は複数のタグが設定出来るブログ記事を想定しています。

記事に紐付けるタグを作ります。リスト形式で中身のオブジェクトは name だけです。

記事データを作ります。こちらもリスト形式でタグの種類は 複数コンテンツ参照 で先程作ったタグを紐付けます。

スキーマが設定終われば、適当なタグと記事の内容を登録しておきます。

GETリクエストで一覧を取得

レスポンスの確認

https://[your-service-name].microcms.io/api/v1/post にリクエストすると、下記のようなレスポンスが取得できます。

{
    "contents": [
        {
            "id": "lxwc41jwt",
            "createdAt": "2020-10-13T11:52:05.153Z",
            "updatedAt": "2020-10-13T11:52:05.153Z",
            "publishedAt": "2020-10-13T11:52:05.153Z",
            "title": "記事2",
            "tag": [
                {
                    "id": "rxayo0c4e",
                    "createdAt": "2020-09-21T06:31:30.797Z",
                    "updatedAt": "2020-09-21T06:31:30.797Z",
                    "publishedAt": "2020-09-21T06:31:30.797Z",
                    "name": "React"
                },
                {
                    "id": "KAYq3aCF0",
                    "createdAt": "2020-09-28T12:12:31.134Z",
                    "updatedAt": "2020-09-28T12:12:31.134Z",
                    "publishedAt": "2020-09-28T12:12:31.134Z",
                    "name": "Next.js"
                }
            ],
            "body": "<p>React と Next.js の記事</p>"
        },
        {
            "id": "aqbxzshbtj",
            "createdAt": "2020-10-13T11:51:19.794Z",
            "updatedAt": "2020-10-13T11:51:19.794Z",
            "publishedAt": "2020-10-13T11:51:19.794Z",
            "title": "記事1",
            "tag": [
                {
                    "id": "Bs89dObS_m",
                    "createdAt": "2020-10-06T14:35:19.719Z",
                    "updatedAt": "2020-10-06T14:35:19.719Z",
                    "publishedAt": "2020-10-06T14:35:19.719Z",
                    "name": "TypeScript"
                },
                {
                    "id": "rxayo0c4e",
                    "createdAt": "2020-09-21T06:31:30.797Z",
                    "updatedAt": "2020-09-21T06:31:30.797Z",
                    "publishedAt": "2020-09-21T06:31:30.797Z",
                    "name": "React"
                }
            ],
            "body": "<p>TypeScript と React の記事</p>"
        }
    ],
    "totalCount": 2,
    "offset": 0,
    "limit": 10
}

型の定義

このレスポンスの型を定義していきます。contents 内の id, createdAt, updatedAt, publishedAt と totalCount, offset, limit は共有のプロパティで毎回使うので apis/common.ts に定義しておきます。

// microCMS から返ってくるオブジェクトの共通プロパティ
export interface CommonItem {
  id: string
  createdAt: string
  updatedAt: string
  publishedAt: string
}

// microCMS から返ってくるリストの共通プロパティ T に上記のオブジェクトが入る
export interface CommonList<T> {
  contents: T[]
  totalCount: number
  offset: number
  limit: number
}

次に apis/post/index.ts を作成します、このパスは API のエンドポイントと同じパスにしてください。

import { CommonItem, CommonList } from '../common'

interface TagItem extends CommonItem {
  name: string
}

// 後述の記事を1件取得する時に使うので export しています
export interface ArticleItem extends CommonItem {
  title: string
  description: string
  tags: TagItem[]
  markdown: string
}

// APIの型定義
export interface Methods {
  get: {
    resBody: CommonList<ArticleItem>
  }
}

aspida の型定義をビルドする

package.json に npm スクリプトを追加します。

{
  "scripts": {
    "api:build": "aspida"
  }
}

yarn api:build を実行すると apis/$api.ts が生成されます。

/* eslint-disable */
import { AspidaClient } from 'aspida'
import { Methods as Methods0 } from './article'

const api = <T>({ baseURL, fetch }: AspidaClient<T>) => {
  const prefix = (baseURL === undefined ? '' : baseURL).replace(/\/$/, '')
  const PATH0 = '/article'
  const GET = 'GET'

  return {
    article: {
      get: (option?: { config?: T }) =>
        fetch<Methods0['get']['resBody']>(prefix, PATH0, GET, option).json(),
      $get: (option?: { config?: T }) =>
        fetch<Methods0['get']['resBody']>(prefix, PATH0, GET, option).json().then(r => r.body),
      $path: () => `${prefix}${PATH0}`
    }
  }
}

export type ApiInstance = ReturnType<typeof api>
export default api

fetch からリクエスト

fetch を使ってリクエストする際には aspida をラッパーに使用します。 microCMS の場合、ヘッダーには X-API-KEY を含める必要があります。

import aspida from '@aspida/fetch'
import api from '~/apis/$api'

const url = 'https://[your-service-name].microcms.io/api/v1'
const config = { headers: {'X-API-KEY': 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' } }

const getList = async () => {
  const _fetch = api(aspida(fetch, { baseURL: `${url}/post` }))

  // res が CommonList<ArticleItem> 型になっている
  const res = await _fetch.article.$get({ config })

}

GETリクエストで記事を 1 件取得

https://[your-service-name].microcms.io/api/v1/post/lxwc41jwt にリクエストすると、下記のようなレスポンスが取得できます。先程取得した一覧の contents の中身が 1 件返ってきます。

{
    "id": "lxwc41jwt",
    "createdAt": "2020-10-13T11:52:05.153Z",
    "updatedAt": "2020-10-13T11:52:05.153Z",
    "publishedAt": "2020-10-13T11:52:05.153Z",
    "title": "記事2",
    "tag": [
        {
            "id": "rxayo0c4e",
            "createdAt": "2020-09-21T06:31:30.797Z",
            "updatedAt": "2020-09-21T06:31:30.797Z",
            "publishedAt": "2020-09-21T06:31:30.797Z",
            "name": "React"
        },
        {
            "id": "KAYq3aCF0",
            "createdAt": "2020-09-28T12:12:31.134Z",
            "updatedAt": "2020-09-28T12:12:31.134Z",
            "publishedAt": "2020-09-28T12:12:31.134Z",
            "name": "Next.js"
        }
    ],
    "body": "<p>React と Next.js の記事</p>"
}

aspida の型定義は apis/article/_contentId@string/index.ts に記述します。 ファイル名の _ で始まる部分がパス変数で @ の後ろに number もしくは string の型を指定します。デフォルトは number | string です。

import { ArticleItem } from '../'

export interface Methods {
  get: {
    resBody: ArticleItem
  }
}

型が定義できたら先ほどと同様に yarn api:build を実行して apis/$api.ts を更新します。

リクエストを投げる際は _contentId() に変数を渡して $get() します。

import aspida from '@aspida/fetch'
import api from '~/apis/$api'

const url = 'https://[your-service-name].microcms.io/api/v1'
const config = { headers: {'X-API-KEY': 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' } }

const getPost = async (id: string) => {
  const _fetch = api(aspida(fetch, { baseURL: `${url}/post` }))

  // res が ArticleItem 型になっている
  const res = await _fetch.article._contentId(id).$get({ config })

}

まとめ

今回は aspida を使って GET レスポンスの型を定義する方法だけを紹介しましたが、 そのほかにもリクエスト時のパラメタ、PUST / PUT / PATCH / DELETE といったほかのメソッドの型も定義できます。