T-CREATOR

Vue.js で API 通信を極める:Axios 徹底活用術

Vue.js で API 通信を極める:Axios 徹底活用術

Web 開発における API 通信は、まるで人と人とのコミュニケーションのようなものです。相手の話を聞き、適切に返答し、時には誤解を解いたり、エラーを修正したりする必要があります。Vue.js と Axios を使った API 通信もまさに同じで、サーバーとの「会話」を円滑に進めるための技術と心構えが必要なのです。

今回の記事では、Vue.js 初心者から中級者の方々が、Axios を使った API 通信をマスターできるよう、実践的な内容を段階的にお伝えしていきます。実際のエラーコードや解決策も含めて、皆さんの開発現場で即戦力となる知識をお届けします。

背景 - なぜ Vue.js で Axios が選ばれるのか

Vue.js エコシステムにおいて、Axios が HTTP クライアントライブラリとして圧倒的な支持を得ている理由は、その直感的な API 設計と豊富な機能にあります。

Vue.js 開発者が Axios を選ぶ理由

#理由具体的なメリット
1Promise ベースの APIasync/await での直感的な非同期処理
2インターセプター機能リクエスト・レスポンスの前処理・後処理
3自動的な JSON 変換データの送受信が簡単
4エラーハンドリング統一されたエラー処理
5ブラウザ・Node.js 対応開発・本番環境での一貫性

Vue.js の reactive なデータバインディングと Axios の Promise ベースの API は、まさに天と地が結ばれるような相性の良さを見せます。データの取得から画面への反映までが、自然な流れで実装できるのです。

従来の fetch API との比較

javascript// fetch API での実装例
try {
  const response = await fetch('/api/users');
  if (!response.ok) {
    throw new Error(
      `HTTP error! status: ${response.status}`
    );
  }
  const data = await response.json();
  this.users = data;
} catch (error) {
  console.error('Error:', error);
}

上記の fetch API による実装は、エラーハンドリングやデータの変換を手動で行う必要があります。一方、Axios を使用した場合は、より簡潔で読みやすいコードになります。

課題 - Vue.js での API 通信でよくある問題

Vue.js 開発者が API 通信で直面する課題は、技術的な問題だけでなく、開発者の心理的な負担も含まれています。

頻繁に発生する問題とその影響

1. CORS (Cross-Origin Resource Sharing) エラー

多くの初心者が最初に遭遇するエラーです。ブラウザのコンソールに以下のようなエラーが表示されます:

csharpAccess to XMLHttpRequest at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

2. 非同期処理の理解不足

Vue.js のライフサイクルと非同期処理の組み合わせで、以下のような問題が発生します:

javascript// 問題のあるコード例
export default {
  data() {
    return {
      users: [],
    };
  },
  created() {
    // この時点では users は空の配列
    this.fetchUsers();
    console.log(this.users); // [] が出力される
  },
  methods: {
    async fetchUsers() {
      this.users = await axios.get('/api/users');
    },
  },
};

3. エラーハンドリングの不備

適切なエラーハンドリングを行わないと、ユーザーは何が起こったのかわからず、混乱してしまいます。

これらの課題は、開発者にとって大きなストレスとなり、時には自信を失う原因にもなります。しかし、適切な知識と実践があれば、これらの問題は必ず解決できるのです。

解決策 - Axios の基本概念と導入方法

Axios は、これらの課題を解決するための強力なツールです。まずは基本的な導入から始めましょう。

プロジェクトへの Axios 導入

Vue.js プロジェクトに Axios を導入する方法をご紹介します。Yarn を使用してパッケージをインストールしましょう。

bash# Axios のインストール
yarn add axios

# TypeScript を使用する場合(型定義も自動的に含まれます)
yarn add axios

基本的な設定とインスタンス作成

API との通信を効率的に行うために、Axios インスタンスを作成して設定を一元管理します。

javascript// src/services/api.js
import axios from 'axios';

// Axios インスタンスを作成
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000, // 10秒でタイムアウト
  headers: {
    'Content-Type': 'application/json',
  },
});

export default apiClient;

この設定により、すべての API 通信で共通の設定を使用できるようになります。baseURL を設定することで、各リクエストで完全な URL を指定する必要がなくなり、保守性が向上します。

Vue.js コンポーネントでの基本的な使用方法

Vue.js コンポーネント内で Axios を使用する基本的なパターンをご紹介します。

javascript// src/components/UserList.vue
<template>
  <div class="user-list">
    <h2>ユーザー一覧</h2>
    <div v-if="loading" class="loading">読み込み中...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <ul v-else>
      <li v-for="user in users" :key="user.id">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
  </div>
</template>

<script>
import apiClient from '@/services/api';

export default {
  name: 'UserList',
  data() {
    return {
      users: [],
      loading: false,
      error: null
    };
  },
  async created() {
    await this.fetchUsers();
  },
  methods: {
    async fetchUsers() {
      this.loading = true;
      this.error = null;

      try {
        const response = await apiClient.get('/users');
        this.users = response.data;
      } catch (error) {
        this.error = 'ユーザーの取得に失敗しました';
        console.error('Error fetching users:', error);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

このコードでは、loading 状態、エラー状態、成功状態を適切に管理しています。ユーザーにとって分かりやすい UI を提供することは、技術的な実装と同じくらい重要です。

具体例

Axios の基本的な使い方

Axios の基本的な使い方を、実践的な例とともに詳しく見ていきましょう。

GET リクエストの実装

データの取得は、最も基本的で頻繁に使用される操作です。

javascript// src/services/userService.js
import apiClient from '@/services/api';

export const userService = {
  // 全ユーザーの取得
  async getAllUsers() {
    try {
      const response = await apiClient.get('/users');
      return {
        success: true,
        data: response.data,
        message: 'ユーザーの取得に成功しました',
      };
    } catch (error) {
      return {
        success: false,
        data: null,
        message: this.handleError(error),
      };
    }
  },

  // 特定ユーザーの取得
  async getUserById(userId) {
    try {
      const response = await apiClient.get(
        `/users/${userId}`
      );
      return {
        success: true,
        data: response.data,
      };
    } catch (error) {
      throw new Error(
        `ユーザー取得エラー: ${error.message}`
      );
    }
  },

  // エラーハンドリング用のヘルパー関数
  handleError(error) {
    if (error.response) {
      // サーバーからのレスポンスがある場合
      return `サーバーエラー: ${error.response.status} - ${error.response.data.message}`;
    } else if (error.request) {
      // リクエストは送信されたが、レスポンスがない場合
      return 'ネットワークエラー: サーバーに接続できません';
    } else {
      // その他のエラー
      return `エラー: ${error.message}`;
    }
  },
};

POST リクエストの実装

新しいデータを作成する際の POST リクエストの実装例です。

javascript// 新規ユーザーの作成
async createUser(userData) {
  try {
    const response = await apiClient.post('/users', {
      name: userData.name,
      email: userData.email,
      password: userData.password
    });

    return {
      success: true,
      data: response.data,
      message: '新規ユーザーを作成しました'
    };
  } catch (error) {
    // バリデーションエラーの詳細処理
    if (error.response && error.response.status === 422) {
      const validationErrors = error.response.data.errors;
      return {
        success: false,
        errors: validationErrors,
        message: 'バリデーションエラーが発生しました'
      };
    }

    throw error;
  }
}

Vue.js プロジェクトへの統合

Vue.js プロジェクトに Axios を統合する際の、実践的なアプローチをご紹介します。

プラグインとしての統合

javascript// src/plugins/axios.js
import axios from 'axios';

const axiosPlugin = {
  install(Vue, options) {
    // Axios インスタンスの作成
    const apiClient = axios.create({
      baseURL: options.baseURL || 'https://api.example.com',
      timeout: options.timeout || 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // リクエストインターセプター
    apiClient.interceptors.request.use(
      (config) => {
        // 認証トークンの自動付与
        const token = localStorage.getItem('authToken');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );

    // レスポンスインターセプター
    apiClient.interceptors.response.use(
      (response) => response,
      (error) => {
        // 認証エラーの場合は自動的にログイン画面に遷移
        if (
          error.response &&
          error.response.status === 401
        ) {
          localStorage.removeItem('authToken');
          window.location.href = '/login';
        }
        return Promise.reject(error);
      }
    );

    // Vue インスタンスにAPIクライアントを追加
    Vue.prototype.$api = apiClient;
  },
};

export default axiosPlugin;

main.js での設定

javascript// src/main.js
import Vue from 'vue';
import App from './App.vue';
import axiosPlugin from '@/plugins/axios';

// Axios プラグインの使用
Vue.use(axiosPlugin, {
  baseURL:
    process.env.VUE_APP_API_BASE_URL ||
    'https://api.example.com',
  timeout: 15000,
});

new Vue({
  render: (h) => h(App),
}).$mount('#app');

Composition API での使用

Vue 3 の Composition API を使用した場合の実装例です。

javascript// src/composables/useApi.js
import { ref, reactive } from 'vue';
import axios from 'axios';

export function useApi() {
  const loading = ref(false);
  const error = ref(null);

  const apiState = reactive({
    data: null,
    error: null,
    loading: false,
  });

  const apiClient = axios.create({
    baseURL: 'https://api.example.com',
    timeout: 10000,
  });

  const request = async (config) => {
    apiState.loading = true;
    apiState.error = null;

    try {
      const response = await apiClient(config);
      apiState.data = response.data;
      return response.data;
    } catch (err) {
      apiState.error = err.message;
      throw err;
    } finally {
      apiState.loading = false;
    }
  };

  const get = (url, config = {}) =>
    request({ method: 'get', url, ...config });
  const post = (url, data, config = {}) =>
    request({ method: 'post', url, data, ...config });
  const put = (url, data, config = {}) =>
    request({ method: 'put', url, data, ...config });
  const del = (url, config = {}) =>
    request({ method: 'delete', url, ...config });

  return {
    apiState,
    loading,
    error,
    get,
    post,
    put,
    delete: del,
  };
}

GET/POST/PUT/DELETE 操作の実装

RESTful API の基本的な CRUD 操作を、実践的な例とともに詳しく見ていきましょう。

GET 操作の詳細実装

javascript// src/services/postService.js
import apiClient from '@/services/api';

export const postService = {
  // 全投稿の取得(ページネーション対応)
  async getAllPosts(page = 1, limit = 10) {
    try {
      const response = await apiClient.get('/posts', {
        params: { page, limit },
      });

      return {
        success: true,
        data: response.data.posts,
        pagination: {
          currentPage: response.data.currentPage,
          totalPages: response.data.totalPages,
          totalItems: response.data.totalItems,
        },
      };
    } catch (error) {
      console.error('投稿取得エラー:', error);
      throw new Error('投稿の取得に失敗しました');
    }
  },

  // 検索機能付きの投稿取得
  async searchPosts(searchQuery, filters = {}) {
    try {
      const response = await apiClient.get(
        '/posts/search',
        {
          params: {
            q: searchQuery,
            category: filters.category,
            sortBy: filters.sortBy || 'createdAt',
            order: filters.order || 'desc',
          },
        }
      );

      return response.data;
    } catch (error) {
      if (error.response?.status === 404) {
        return {
          posts: [],
          message: '検索結果が見つかりませんでした',
        };
      }
      throw error;
    }
  },
};

POST 操作の詳細実装

javascript// 新規投稿の作成
async createPost(postData) {
  try {
    const response = await apiClient.post('/posts', {
      title: postData.title,
      content: postData.content,
      category: postData.category,
      tags: postData.tags || [],
      isPublished: postData.isPublished || false
    });

    return {
      success: true,
      data: response.data,
      message: '投稿を作成しました'
    };
  } catch (error) {
    // バリデーションエラーの詳細処理
    if (error.response?.status === 422) {
      const errors = error.response.data.errors;
      return {
        success: false,
        errors: errors,
        message: '入力内容に問題があります'
      };
    }

    // 認証エラー
    if (error.response?.status === 401) {
      throw new Error('認証が必要です。ログインしてください。');
    }

    // その他のエラー
    throw new Error(`投稿の作成に失敗しました: ${error.message}`);
  }
}

PUT 操作の詳細実装

javascript// 既存投稿の更新
async updatePost(postId, postData) {
  try {
    const response = await apiClient.put(`/posts/${postId}`, {
      title: postData.title,
      content: postData.content,
      category: postData.category,
      tags: postData.tags,
      isPublished: postData.isPublished,
      updatedAt: new Date().toISOString()
    });

    return {
      success: true,
      data: response.data,
      message: '投稿を更新しました'
    };
  } catch (error) {
    // 404エラー(投稿が見つからない)
    if (error.response?.status === 404) {
      throw new Error('指定された投稿が見つかりません');
    }

    // 403エラー(権限なし)
    if (error.response?.status === 403) {
      throw new Error('この投稿を編集する権限がありません');
    }

    throw new Error(`投稿の更新に失敗しました: ${error.message}`);
  }
}

DELETE 操作の詳細実装

javascript// 投稿の削除
async deletePost(postId) {
  try {
    await apiClient.delete(`/posts/${postId}`);

    return {
      success: true,
      message: '投稿を削除しました'
    };
  } catch (error) {
    // 404エラー
    if (error.response?.status === 404) {
      throw new Error('指定された投稿が見つかりません');
    }

    // 403エラー
    if (error.response?.status === 403) {
      throw new Error('この投稿を削除する権限がありません');
    }

    throw new Error(`投稿の削除に失敗しました: ${error.message}`);
  }
}

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

javascript// src/components/PostManager.vue
<template>
  <div class="post-manager">
    <h2>投稿管理</h2>

    <!-- 新規投稿フォーム -->
    <form @submit.prevent="createPost" class="post-form">
      <input
        v-model="newPost.title"
        type="text"
        placeholder="タイトル"
        required
      />
      <textarea
        v-model="newPost.content"
        placeholder="内容"
        required
      ></textarea>
      <button type="submit" :disabled="isSubmitting">
        {{ isSubmitting ? '投稿中...' : '投稿する' }}
      </button>
    </form>

    <!-- 投稿一覧 -->
    <div class="posts-list">
      <article
        v-for="post in posts"
        :key="post.id"
        class="post-item"
      >
        <h3>{{ post.title }}</h3>
        <p>{{ post.content }}</p>
        <div class="post-actions">
          <button @click="editPost(post)">編集</button>
          <button @click="deletePost(post.id)" class="delete-btn">削除</button>
        </div>
      </article>
    </div>
  </div>
</template>

<script>
import { postService } from '@/services/postService';

export default {
  name: 'PostManager',
  data() {
    return {
      posts: [],
      newPost: {
        title: '',
        content: ''
      },
      isSubmitting: false,
      error: null
    };
  },
  async created() {
    await this.fetchPosts();
  },
  methods: {
    async fetchPosts() {
      try {
        const result = await postService.getAllPosts();
        this.posts = result.data;
      } catch (error) {
        this.error = error.message;
      }
    },

    async createPost() {
      this.isSubmitting = true;

      try {
        const result = await postService.createPost(this.newPost);

        if (result.success) {
          this.posts.unshift(result.data);
          this.newPost = { title: '', content: '' };
          this.$emit('success', result.message);
        }
      } catch (error) {
        this.error = error.message;
      } finally {
        this.isSubmitting = false;
      }
    }
  }
};
</script>

インターセプターの活用

インターセプターは、リクエストやレスポンスを自動的に処理する強力な機能です。認証、ログ、エラー処理などを一元化できます。

リクエストインターセプターの実装

javascript// src/interceptors/request.js
import apiClient from '@/services/api';

// リクエストインターセプターの設定
apiClient.interceptors.request.use(
  (config) => {
    // 認証トークンの自動付与
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    // リクエストログの出力
    console.log(
      `[REQUEST] ${config.method?.toUpperCase()} ${
        config.url
      }`
    );

    // タイムスタンプの追加
    config.metadata = { startTime: new Date() };

    return config;
  },
  (error) => {
    console.error('[REQUEST ERROR]', error);
    return Promise.reject(error);
  }
);

レスポンスインターセプターの実装

javascript// レスポンスインターセプターの設定
apiClient.interceptors.response.use(
  (response) => {
    // レスポンス時間の計算
    const endTime = new Date();
    const duration =
      endTime - response.config.metadata.startTime;

    console.log(
      `[RESPONSE] ${response.config.method?.toUpperCase()} ${
        response.config.url
      } - ${duration}ms`
    );

    return response;
  },
  (error) => {
    // エラーレスポンスの詳細処理
    if (error.response) {
      const { status, data } = error.response;

      switch (status) {
        case 401:
          // 認証エラー:自動的にログアウト
          localStorage.removeItem('authToken');
          window.location.href = '/login';
          break;

        case 403:
          // 権限エラー
          console.error(
            '[FORBIDDEN] アクセス権限がありません'
          );
          break;

        case 404:
          // Not Found エラー
          console.error(
            '[NOT FOUND] リソースが見つかりません'
          );
          break;

        case 422:
          // バリデーションエラー
          console.error('[VALIDATION ERROR]', data.errors);
          break;

        case 500:
          // サーバーエラー
          console.error(
            '[SERVER ERROR] サーバーで問題が発生しました'
          );
          break;

        default:
          console.error(`[ERROR ${status}]`, data.message);
      }
    } else if (error.request) {
      // ネットワークエラー
      console.error(
        '[NETWORK ERROR] サーバーに接続できません'
      );
    }

    return Promise.reject(error);
  }
);

高度なインターセプター:リトライ機能

javascript// src/interceptors/retry.js
import apiClient from '@/services/api';

// リトライ機能付きのインターセプター
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    // リトライ条件の判定
    if (
      error.response?.status >= 500 &&
      !originalRequest._retry &&
      originalRequest.retryCount < 3
    ) {
      originalRequest._retry = true;
      originalRequest.retryCount =
        (originalRequest.retryCount || 0) + 1;

      // 指数バックオフでリトライ
      const delay =
        Math.pow(2, originalRequest.retryCount) * 1000;
      await new Promise((resolve) =>
        setTimeout(resolve, delay)
      );

      console.log(
        `[RETRY] ${
          originalRequest.retryCount
        }/3 - ${originalRequest.method?.toUpperCase()} ${
          originalRequest.url
        }`
      );

      return apiClient(originalRequest);
    }

    return Promise.reject(error);
  }
);

エラーハンドリングの実装

適切なエラーハンドリングは、ユーザーエクスペリエンスを大きく左右します。実際のエラーコードとともに、包括的なエラーハンドリング戦略をご紹介します。

包括的なエラーハンドリングクラス

javascript// src/utils/errorHandler.js
export class ApiError extends Error {
  constructor(message, status, data) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.data = data;
  }
}

export const errorHandler = {
  // HTTP ステータスコードに基づくエラー処理
  handleHttpError(error) {
    if (!error.response) {
      return {
        type: 'NETWORK_ERROR',
        message:
          'ネットワークエラーが発生しました。インターネット接続を確認してください。',
        userMessage: 'インターネット接続を確認してください',
      };
    }

    const { status, data } = error.response;

    switch (status) {
      case 400:
        return {
          type: 'BAD_REQUEST',
          message: 'リクエストの内容に問題があります',
          userMessage: '入力内容を確認してください',
          details: data.errors || [],
        };

      case 401:
        return {
          type: 'UNAUTHORIZED',
          message: '認証が必要です',
          userMessage: 'ログインしてください',
          action: 'redirect_login',
        };

      case 403:
        return {
          type: 'FORBIDDEN',
          message: 'アクセス権限がありません',
          userMessage: 'この操作を実行する権限がありません',
        };

      case 404:
        return {
          type: 'NOT_FOUND',
          message: 'リソースが見つかりません',
          userMessage:
            'お探しのページまたはデータが見つかりません',
        };

      case 422:
        return {
          type: 'VALIDATION_ERROR',
          message: 'バリデーションエラーが発生しました',
          userMessage: '入力内容に問題があります',
          details: data.errors || [],
        };

      case 429:
        return {
          type: 'RATE_LIMIT',
          message: 'リクエストが多すぎます',
          userMessage:
            'しばらく時間をおいてから再度お試しください',
        };

      case 500:
        return {
          type: 'SERVER_ERROR',
          message: 'サーバーで問題が発生しました',
          userMessage:
            'サーバーで問題が発生しました。しばらく時間をおいてから再度お試しください',
        };

      default:
        return {
          type: 'UNKNOWN_ERROR',
          message: `予期しないエラーが発生しました (${status})`,
          userMessage: '予期しないエラーが発生しました',
        };
    }
  },

  // Vue.js コンポーネントでの使用を想定したエラーハンドラー
  async handleApiCall(apiCall, context) {
    try {
      context.loading = true;
      context.error = null;

      const result = await apiCall();
      return result;
    } catch (error) {
      const errorInfo = this.handleHttpError(error);
      context.error = errorInfo;

      // 自動的にログイン画面に遷移
      if (errorInfo.action === 'redirect_login') {
        context.$router.push('/login');
      }

      throw new ApiError(
        errorInfo.message,
        error.response?.status,
        errorInfo
      );
    } finally {
      context.loading = false;
    }
  },
};

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

javascript// src/components/UserProfile.vue
<template>
  <div class="user-profile">
    <h2>ユーザープロフィール</h2>

    <!-- ローディング状態 -->
    <div v-if="loading" class="loading">
      <div class="spinner"></div>
      <p>読み込み中...</p>
    </div>

    <!-- エラー状態 -->
    <div v-else-if="error" class="error-container">
      <div class="error-message">
        <h3>エラーが発生しました</h3>
        <p>{{ error.userMessage }}</p>

        <!-- バリデーションエラーの詳細表示 -->
        <div v-if="error.details && error.details.length > 0" class="error-details">
          <h4>詳細:</h4>
          <ul>
            <li v-for="detail in error.details" :key="detail.field">
              {{ detail.field }}: {{ detail.message }}
            </li>
          </ul>
        </div>

        <button @click="retryLoad" class="retry-btn">再試行</button>
      </div>
    </div>

    <!-- 正常状態 -->
    <div v-else class="profile-content">
      <div class="profile-info">
        <h3>{{ user.name }}</h3>
        <p>{{ user.email }}</p>
      </div>

      <form @submit.prevent="updateProfile" class="profile-form">
        <input v-model="editUser.name" type="text" placeholder="名前" />
        <input v-model="editUser.email" type="email" placeholder="メールアドレス" />
        <button type="submit" :disabled="isUpdating">
          {{ isUpdating ? '更新中...' : '更新' }}
        </button>
      </form>
    </div>
  </div>
</template>

<script>
import { userService } from '@/services/userService';
import { errorHandler } from '@/utils/errorHandler';

export default {
  name: 'UserProfile',
  data() {
    return {
      user: null,
      editUser: { name: '', email: '' },
      loading: false,
      error: null,
      isUpdating: false
    };
  },
  async created() {
    await this.loadUserProfile();
  },
  methods: {
    async loadUserProfile() {
      try {
        await errorHandler.handleApiCall(
          () => userService.getCurrentUser(),
          this
        );

        this.user = await userService.getCurrentUser();
        this.editUser = { ...this.user };
      } catch (error) {
        // エラーハンドリングは errorHandler で処理済み
        console.error('ユーザー情報の読み込みに失敗:', error);
      }
    },

    async updateProfile() {
      this.isUpdating = true;

      try {
        await errorHandler.handleApiCall(
          () => userService.updateUser(this.editUser),
          this
        );

        this.user = await userService.updateUser(this.editUser);
        this.$emit('success', 'プロフィールを更新しました');
      } catch (error) {
        console.error('プロフィール更新に失敗:', error);
      } finally {
        this.isUpdating = false;
      }
    },

    async retryLoad() {
      await this.loadUserProfile();
    }
  }
};
</script>

グローバルエラーハンドラーの設定

javascript// src/main.js
import Vue from 'vue';
import App from './App.vue';
import { errorHandler } from '@/utils/errorHandler';

// グローバルエラーハンドラーの設定
Vue.config.errorHandler = (err, vm, info) => {
  console.error('グローバルエラー:', err);
  console.error('コンポーネント:', vm);
  console.error('詳細情報:', info);

  // エラー報告サービスにエラーを送信
  // 例: Sentry, LogRocket など
};

// プロトタイプにエラーハンドラーを追加
Vue.prototype.$handleError = errorHandler.handleHttpError;

new Vue({
  render: (h) => h(App),
}).$mount('#app');

まとめ

Vue.js と Axios を使った API 通信の世界を探求する旅は、いかがでしたでしょうか。この記事では、基本的な使い方から高度なエラーハンドリングまで、実践的な内容を段階的にお伝えしました。

重要なポイントの振り返り

#ポイント価値
1適切なエラーハンドリングユーザーエクスペリエンスの向上
2インターセプターの活用効率的な認証・ログ処理
3一元化された API 設定保守性の向上
4型安全性の確保バグの早期発見
5レスポンシブな UI 状態管理直感的なユーザーインターフェース

API 通信は、単なる技術的な処理ではありません。それは、あなたのアプリケーションとユーザーを繋ぐ大切な架け橋です。エラーが発生したときに適切なメッセージを表示し、ローディング状態を分かりやすく示し、成功時には喜びを共有する。そんな心のこもった実装こそが、本当に価値のあるアプリケーションを作り上げるのです。

次のステップ

今回学んだ内容を基に、ぜひ実際のプロジェクトで実践してみてください。最初は小さな機能から始めて、徐々に複雑な処理にチャレンジしていきましょう。

  • GraphQL との組み合わせを検討してみる
  • WebSocket を使ったリアルタイム通信に挑戦
  • Progressive Web App (PWA) での API 通信を実装
  • テスト駆動開発 でより堅牢なコードを書く

皆さんの開発活動が、より豊かで創造的なものになることを心より願っています。

関連リンク