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の型がつく