0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React + TypeScript で API クライアントを統合設計する - JWT 認証の自動化と axios 直接使用の撲滅

Posted at

フィットトラッキングアプリ開発、Railway×Node.jsに挑戦中の
転職準備中のプログラマー未経験初心者です。

今回はデバッグ用のAPI_BASE_URLをWebコンソールに表示して、
APIエンドポイントの設定が狙い通りの場所にアクセスしているか確認を行いたかったが、
webコンソールにエンドポイントが表示されないバグの、検証したことや根本的な原因についてまとめた内容です

  • フレームワーク / ライブラリ:
    • React + TypeScript
    • Axios
    • Vite
  • ゴール(何を実装したかったか):
    • AuthContext.tsx と api.ts の統合設計を実装
    • 統一された API クライアント(apiClient)を使用して JWT 認証を自動化
    • デバッグ用の console.log で API_BASE_URL を確認
  • 作業環境:
    • OS: macOS darwin 24.6.0
    • Node.js: 最新版
    • その他: React 18, TypeScript, Vite

2. 発生した問題

時刻 現象 エラーメッセージ
開発中 デバッグ用の console.log で API_BASE_URL が表示されない 🌐 API Base URL: http://localhost:8000 が表示されない
開発中 AuthContext.tsx で axios を直接使用している箇所が残っている 統合設計が不完全
開発中 複数のファイルで axios の直接使用が散在している コードの一貫性が欠如

再現条件:

  1. api.ts ファイルで console.log('🌐 API Base URL:', API_BASE_URL)を記述
  2. ブラウザの開発者ツールでコンソールを確認
  3. 期待されるログが表示されない

関連コード:

// api.ts (問題のコード)
const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
console.log("🌐 API Base URL:", API_BASE_URL);

// AuthContext.tsx (問題のあったコード)
const setAuthToken = (token: string | null): void => {
  if (token) {
    axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
  } else {
    delete axios.defaults.headers.common["Authorization"];
  }
};

3. 仮説と検証

仮説 検証手順 結果 新たな気づき
api.ts ファイルがインポートされていない ファイルのインポート状況を確認 ✅ インポートされている api.ts は作成済み
AuthContext.tsx でまだ axios を直接使用している AuthContext.tsx のコード確認 ✅ 仮説通り 統合が不完全
複数のファイルで axios の直接使用が残っている grep 検索で確認 ✅ Dashboard.jsx, WorkoutHistory.jsx, WorkoutForm.jsx 3 箇所で修正が必要
apiClient のインターセプターが正しく動作していない インターセプターの設定確認 ✅ 設定は正しい 問題は使用箇所

デバッグで使ったツール/手法:

  • ✅ console.log
  • ✅ デバッガー
  • ✅ ネットワークタブ
  • ✅ grep 検索
  • ✅ ファイル内容確認

4. 根本的な原因 / 知識のギャップ

真の原因:

  1. 設計の理解不足: AuthContext.tsx と api.ts の役割分担が不明確
  2. 統合の不完全性: 一部のファイルでまだ axios を直接使用
  3. console.log の実行タイミング: api.ts のインポート時に実行されるが、実際の API 呼び出し時には表示されない

自分の知識で足りなかった部分:

  • 基本概念: axios インターセプターの動作原理
  • フレームワーク仕様: React Context と API クライアントの統合設計
  • 環境設定: Vite での環境変数の読み込みタイミング
  • その他: モジュールの読み込み順序と console.log の実行タイミング

エラーの分類: ✅ ロジック ✅ 設計

5. 最終的な解決策

解決コード:

1. 統合された API クライアント (api.ts)

// api.ts - 統合設計版
import axios, { AxiosInstance, InternalAxiosRequestConfig } from "axios";

const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";

// カスタムaxiosインスタンスの作成
const apiClient: AxiosInstance = axios.create({
  baseURL: API_BASE_URL,
  withCredentials: true,
  headers: {
    "Content-Type": "application/json",
  },
});

// リクエストインターセプター - JWT自動付与
apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const token = localStorage.getItem("token");
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    // デバッグログ(リクエスト時に表示)
    console.log("🌐 API Request:", {
      url: `${config.baseURL}${config.url}`,
      method: config.method,
      hasToken: !!token,
    });

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// レスポンスインターセプター - エラーハンドリング
apiClient.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    if (error.response?.status === 401) {
      console.error(
        '401 Unauthorized - JWTトークンが無効または期限切れです'
      );
      localStorage.removeItem('token');
    }
    return Promise.reject(error);
  }
);

export default apiClient;

2. AuthContext.tsx の修正

// AuthContext.tsx - 統合後のコード
import React, { createContext, useState, useContext, ReactNode } from "react";
import apiClient from "../services/api";
import { User, LoginCredentials, LoginResponse } from "../types";

interface AuthContextType {
  user: User | null;
  login: (credentials: LoginCredentials) => Promise<User>;
  logout: () => void;
  loading: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC<{ children: ReactNode }> = ({
  children,
}) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  // axios直接使用を削除し、apiClientを使用
  const login = async (credentials: LoginCredentials): Promise<User> => {
    try {
      const res = await apiClient.post<LoginResponse>(
        "/authrouter/login",
        credentials
      );
      const { token, user } = res.data;

      // トークンを保存(インターセプターが自動的に使用)
      localStorage.setItem("token", token);
      setUser(user);

      return user;
    } catch (error) {
      console.error("Login failed:", error);
      throw error;
    }
  };

  const logout = () => {
    localStorage.removeItem("token");
    setUser(null);
  };

3. コンポーネントでの使用例

// Dashboard.jsx - 修正後
import apiClient from "../services/api";

const Dashboard = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        // axios → apiClient に変更
        const response = await apiClient.get("/api/dashboard");
        setData(response.data);
      } catch (error) {
        console.error("Failed to fetch dashboard data:", error);
      }
    };

    fetchData();
  }, []);

  return <div>{/* Dashboard UI */}</div>;
};

設定変更:

  • ファイル名: Dashboard.jsx → 変更内容: axios → apiClient
  • ファイル名: WorkoutHistory.jsx → 変更内容: axios → apiClient
  • ファイル名: WorkoutForm.jsx → 変更内容: axios → apiClient

解決に至った決め手:

  • 統合設計の理解: AuthContext.tsx と api.ts の役割分担を明確化
  • インターセプターの活用: 手動のトークン設定を自動化
  • 一貫性の確保: 全ファイルで apiClient の統一使用

6. 応用と予防

目標:類似問題への対処法を準備

類似問題の予測

このパターンで起こりそうな他の問題:

  1. 他の API クライアントライブラリでの同様の問題
  2. 異なる認証方式での統合設計の複雑化
  3. マイクロフロントエンドでの API クライアント管理

予防策の立案

今後同じ問題を防ぐ方法:

  • コードレビューでチェック: axios の直接使用を禁止
  • 自動テスト追加: API クライアントの統合テスト
  • 設定ファイル整備: ESLint ルールで axios 直接使用を検出
  • チーム内ルール: API クライアントの統一使用をルール化

ESLint ルールの設定例

// .eslintrc.js
module.exports = {
  rules: {
    "no-restricted-imports": [
      "error",
      {
        paths: [
          {
            name: "axios",
            message: "Please use apiClient from services/api instead.",
          },
        ],
      },
    ],
  },
};

応用アイデア

他のプロジェクト/機能で活かせそうなこと:

  • プロジェクト: 認証システム → 活用方法: JWT インターセプターの自動化
  • 機能: エラーハンドリング → 活用方法: 統一されたエラー処理

7. 実践での活用

目標:学習を実際の開発力向上につなげる

即座に適用

今日から意識すること:

  • API クライアントの統一使用
  • インターセプターの活用
  • 手動設定の最小化

効率化アイデア

今回の経験でわかった効率化のヒント:

  • デバッグ手順: grep 検索で問題箇所を特定
  • ツール活用: ESLint でコード品質を自動チェック
  • 情報収集: console.log の配置と実行タイミングの理解

9. 振り返りと評価

学習効果

今回のデバッグで身についたスキル (1-5 点):

  • 問題特定力: 4 点 - grep 検索で効率的に問題箇所を特定
  • 仮説構築力: 4 点 - 設計の問題を正確に特定
  • 検証効率: 3 点 - 段階的な検証で原因を絞り込み
  • 解決スピード: 4 点 - 統合設計により根本解決

改善点

次回もっと早く解決するには:

  • 設計段階での統一方針の明確化
  • コードレビューでの早期発見

成長実感

以前の自分と比べて成長した点:

  • 設計思考: 個別の修正ではなく統合的な解決策を考えるようになった
  • 効率性: grep 検索などのツールを活用した問題特定ができるようになった

10. クイックリファレンス

同じエラーに遭遇した時にすぐ教えられる情報

チェックポイント:

  1. axios の直接使用箇所を grep 検索
  2. apiClient のインポート状況確認
  3. インターセプターの設定確認

解決の順序:

  1. 問題箇所の特定 (grep -r "axios" src/)
  2. apiClient への置き換え
  3. 手動設定の削除

関連する過去のエラー

  • 日付: 2024/01/25 → 関連内容: 認証ヘッダー設定忘れ
  • 日付: 2024/01/26 → 関連内容: API クライアント設定の不整合

💡 今日の一言学習メモ

「統合設計は個別修正よりも効率的。インターセプターの自動化で人的ミスを排除し、保守性を向上させる」

アーキテクチャ図

┌─────────────────────────────────────────────┐
│                  React App                   │
├───────────────┬──────────────┬──────────────┤
│  Dashboard    │ WorkoutForm  │ WorkoutHist  │
└───────┬───────┴──────┬───────┴──────┬───────┘
        │              │              │
        └──────────────┼──────────────┘
                       ▼
              ┌──────────────┐
              │   apiClient  │ ← 統一されたAPIクライアント
              │              │
              │ Interceptors │ ← JWT自動付与
              └──────┬───────┘
                     │
                     ▼
              ┌──────────────┐
              │  Backend API │
              └──────────────┘

この統合設計により、認証処理が一元化され、保守性と開発効率が大幅に向上しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?