TypeScriptのaxios.get<Response>はレスポンスの型を検証してないので注意が必要

axios.getはレスポンスの型を検証してない

色々なプロジェクトのTypeScriptのコードを見ていると、たまにaxios.get<Response>のようなコードを見かけます。

例えば以下のようなコードです。

sample.ts
import axios from 'axios';

type Products = {
  id: string
  title: string
  // 省略
}

type ProductsResponse = {
  products: Products[]
  total: number
  skip: number
  limit: number
}

axios.get<ProductsResponse>('https://dummyjson.com/products?limit=2')
  .then((res) => {
    const data = res.data;
    console.log(data)
  })

TypeScript初心者だと、このコードで実行時のレスポンスの型の検証ができると思っている方を見かけますが、このコードではレスポンスの型の検証は実行されません。

そのため、totalの型はstringではなくnumberが正しいですが、エラーにならずに実行されます。

もし本当に厳密にレスポンスをチェックしたい場合、以下のようにtypeofで型を検証する必要があります。

sample.ts
import axios from 'axios'

type Product = {
  id: string
  title: string
  // 省略
}

type ProductsResponse = {
  products: Product[]
  total: number
  skip: number
  limit: number
}

function isProduct(obj: any): obj is Product {
  return (
    typeof obj.id === 'string' &&
    typeof obj.title === 'string' &&
    typeof obj.description === 'string'
  )
}

function isProductsResponse(obj: any): obj is ProductsResponse {
  return (
    Array.isArray(obj.products) &&
    obj.products.every(isProduct) &&
    typeof obj.total === 'number' &&
    typeof obj.skip === 'number' &&
    typeof obj.limit === 'number'
  )
}

axios.get('https://dummyjson.com/products?limit=2')
  .then((response) => {
    if (isProductsResponse(response.data)) {
      console.log(response.data)
    } else {
      console.error('❌️ レスポンスの検証中にエラーが発生しました')
        }
      })
      .catch(() => {
        console.error('❌️ レスポンスの検証中にエラーが発生しました')
  })

しかし、これだとコード量が多くなって効率も悪いため、一般的にはZodライブラリが使用されます。

Zodライブラリとは

ZodはTypeScript向けの型安全なスキーマバリデーションライブラリです。

簡単に言うと、TypeScript の型を実行時でもチェックできるようにするツールです。

インストールは「npm i zod」でできます。

Zodを使用すると先程のコードは以下のようになります。

typeおよびtypeofがなくなってかなり見やすいコードになりました。

sample.ts
import axios from 'axios'
import { z } from 'zod'

const ProductSchema = z.object({
  id: z.string(),
  title: z.string(),
  // 省略
})

const ProductsResponseSchema = z.object({
  products: z.array(ProductSchema),
  total: z.number(),
  skip: z.number(),
  limit: z.number(),
})

const result = document.getElementById('result')!

axios
  .get('https://dummyjson.com/products?limit=2')
  .then((res) => {
    const parseResult = ProductsResponseSchema.safeParse(res.data)
    if (parseResult.success) {
      console.log('✅️ レスポンスの検証に成功しました')
      result.textContent = JSON.stringify(parseResult.data, null, 2)
    } else {
      result.textContent =
        '❌️ レスポンスの検証中にエラーが発生しました\n' + parseResult.error
    }
  })
  .catch((err) => {
    result.textContent = '❌️ レスポンスの検証中にエラーが発生しました\n' + err
  })

Zodは z.number().min(5)z.string().email() のようにメソッドをつなげることで型以外の検証も追加できます。

以下のサンプルを作成しましたので、実行結果を見比べてみてください。

axiosのtotal: stringのコードの場合はエラーになっていませんが、axios + Zodのほうのサンプルは total: z.string() のバリデーションに失敗して、エラーが発生していることが確認できます。

TypeScript axios sample

TypeScript axios + Zod sample