AxiosにJWTつけてレスポンスに型もつけたい
Created at: Last updated at:
JWTを用いたAPIリクエストをするSPAをReactで作っていた中で、全てのAxiosのリクエストのヘッダーにJWTを付与してさらにレスポンスに型をつけたかった。
SPAからバックエンドサーバーに通信する際のリクエストヘッダーにBearer: {JWT}
を付与したく、さらにはJWTの有効期限が切れている場合は勝手にJWTを更新した上でリクエストをする仕組みが欲しかった。
axiosのレスポンスに型をつける
Axiosの型定義を見るとAxiosResponce<T = any>
といった形でレスポンスの型情報1に型パラメータが使える。
export interface AxiosResponse<T = any> {
data: T; // 👈
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request?: any;
}
しかし、Axiosは非同期的に使うと思うのでawait axios.get()
の返り値はAxiosPromise
ですが、以下のような型定義2なので同じとみなせます。
export interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> {}
ということは、以下のようなコードがかけます。
https://example.com/api/
のレスポンスが以下の型である時
interface ApiResponceType {
name: string
id: string
}
リクエストするAxiosのコードは
import axios, { AxiosRequestConfig, AxiosResponce } from 'axios'
const responce = await axios.get<ApiResponceType>('https://example.com/api/')
responce.data // ここの型がApiResponceTypeになる
Bearerヘッダーを付与・JWTの有効期限も考慮する
axiosのインスタンスを作り、そこにBearerヘッダーを付与しておきます。さらにaxiosのrequest/responceにミドルウェア的に処理を挟み込める仕組みとしてinterrceptors4があるので、これを用いてJWTの有効期限が切れていた時はJWTの更新・再リクエストをおこないます。
import axios, { AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios'
export const apiClient = async <T, R = AxiosResponse<T>>(
url: string,
method: 'get' | 'post',
data?: any,
headers?: Map<string, string>,
...requestConfig: any
): Promise<R> => {
if (!user) {
throw new Error('undefined currentuser')
}
const jwtToken = getJwtToken() // 👈 任意の方法で保持しているJWT
const config: AxiosRequestConfig = {
...requestConfig,
url: url,
method: method,
data,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jwtToken}`,
...headers,
},
}
const customAxios = axios.create()
let isRetry = false
customAxios.interceptors.response.use(
(res: AxiosResponse<T>) => res,
async (error: AxiosError) => {
if (error.response?.status === 401 && isRetry) {
// 👈 JWT有効期限切れかつ一回目のリクエストである時
isRetry = true
await refreshJwtToken()
const originalRequestConfig = error.config
return customAxios.request(originalRequestConfig) // 👈 再度リクエスト
} else {
return Promise.reject(error)
}
}
)
return customAxios.request(config)
}
const responce = apiClient<ApiResponceType>('https://example.com/api/', { id: 1 })
responce.data // 👈 ApiResponceTypeの型がつく