はじめに
フロントエンド開発では、APIとの連携がほぼ必須です。しかし、「リクエストごとに認証トークンを手動で付与する」「エラーが発生するたびにtry-catchを書く」といった定型的な処理は、面倒でバグの原因になりがちです。
本記事では、Axiosインターセプターを使って、これらの処理を自動化し、コードを簡潔にする方法を解説します。
Axiosのインターセプターとはどういう機能か
Axiosのインターセプターは、HTTPリクエスト・レスポンスの送受信を横取り(インターセプト)し、共通の前処理・後処理を実行する仕組みです。
これにより、すべてのリクエストに対して、同じロジックを一度だけ書けば済むようになります。
-
リクエストインターセプター
: リクエストがサーバーに送られる前に実行されます。- 認証トークンをヘッダーに追加する。
- リクエストのデータを加工する。
-
レスポンスインターセプター
: レスポンスがコンポーネントに届く前に実行されます。- 401 Unauthorizedエラーを検知し、自動でトークンを再発行する
- APIから返されたエラーメッセージを統一的に処理する。
Axiosのインターセプターを実装してみる
Axiosクライアントのインスタンスを作成する
まず、共通の設定を持つAxiosクライアントのインスタンスを作成します。これにより、アプリケーション全体で同じ設定を共有できます。
// src/api/axiosClient.ts
import axios, { AxiosInstance } from 'axios';
// APIのベースURLを環境変数から取得
const BASE_URL = import.meta.env.VITE_API_BASE_URL;
// Axiosのインスタンスを作成
const axiosClient: AxiosInstance = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
},
timeout: 10000, // 10秒でタイムアウト
});
export default axiosClient;
リクエストインターセプターで認証トークンを自動付与する
次に、APIリクエストを送信する際に、認証トークンをAuthorizationヘッダーに自動で付与するインターセプターを設定します。これにより、トークンを毎回手動で設定する必要がなくなります。
// src/api/axiosClient.ts (追記)
import { useAppStore } from '../stores';
axiosClient.interceptors.request.use(
(config) => {
// Zustandストアからトークンを取得
const { userToken } = useAppStore.getState();
// トークンが存在する場合、Authorizationヘッダーに追加
if (userToken) {
config.headers['Authorization'] = `Bearer ${userToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
レスポンスインターセプターで認証エラーをハンドリングする
レスポンスインターセプターは、APIから401 Unauthorizedエラーが返ってきた場合に、ログイン画面にリダイレクトするロジックを実装することができます。
※ 実際の運用では、401エラー時にリフレッシュトークンを使ってアクセストークンを再発行し、再試行する実装も一般的です。
本記事では簡略化のため省略しています。
// src/api/axiosClient.ts (追記)
import { useAppStore } from '../stores';
axiosClient.interceptors.response.use(
(response) => response,
async (error) => {
// 401認証エラーの場合
if (error.response?.status === 401) {
// ログアウト処理
useAppStore.getState().logout();
window.location.href = '/login';
}
return Promise.reject(error);
}
);
APIの利用例
インターセプターを設定すれば、コンポーネントはAPI通信の複雑なロジックを意識する必要がなくなります。
import axiosClient from '../api/axiosClient';
const fetchUserProfile = async () => {
try {
// 認証ヘッダーを意識することなくAPIを呼び出せる
const response = await axiosClient.get('/profile');
console.log('ユーザー情報:', response.data);
} catch (error) {
// APIエラーを統一的にハンドリングできる
console.error('API呼び出しエラー:', error.message);
}
};
補足:認証不要のAPIではクライアントを分ける
今回の記事では、認証トークンが必要なAPIへの対応にフォーカスしましたが、ログインやパスワードリセットのように認証前でも利用できるAPIがあるケースも多いと思います。
その場合、Axiosクライアントを「認証付き(例: authClient
)」と「認証不要(例: publicClient
)」で分けて管理するのが一般的です。
用途ごとに命名・設計を明確にしておくことで、以下のメリットがあります。
- 認証不要APIに誤ってトークンを送ってしまうことを防げる
- インターセプターの設定をクライアントごとに独立管理できる
- テストやモックが簡単になる
認証なしのAPIクライアント
// src/api/publicClient.ts
import axios from 'axios';
const publicClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
timeout: 10000,
});
export default publicClient;
認証なしのAPI利用例
import publicClient from '../api/publicClient';
export const login = async (credentials) => {
const response = await publicClient.post('/auth/login', credentials);
return response.data;
};
認証ありのAPI利用例
import authClient from '../api/authClient';
export const getProfile = async () => {
const response = await authClient.get('/profile');
return response.data;
};
おわりに
Axiosインターセプターを使うことで、認証トークンの管理やエラーハンドリングといった定型的な処理を自動化し、API通信のロジックをコンポーネントから完全に分離できます。
次回以降の記事では、本記事をベースに、MSWを使ったAxiosのモック方法、loaderを使ったデータ取得やZustandでの状態管理など、より実践的な開発手法を解説していきます。