Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

fetch APIの基本

JavaScriptでAPIリクエストを送るためのfetch APIについて学んでいきましょう。前回Thunder Clientで体験したAPIリクエストを、今度はコードで実装してみます。

fetch APIとは

fetch APIは、JavaScriptでHTTPリクエストを送るためのモダンな方法です。ブラウザに標準搭載されており、Promiseベースで使いやすく設計されています(昔のXMLHttpRequestより遥かに簡単です)。

fetch APIの特徴

  • Promise ベース: async/await で読みやすいコードが書ける
  • 標準搭載: 追加ライブラリ不要
  • 柔軟: あらゆるHTTPリクエストに対応
  • モダン: 現代的なJavaScriptの書き方

基本的な使い方

最もシンプルなGETリクエスト

// 基本形
fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('エラー:', error));

// async/await版(推奨)
async function getPost() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('エラー:', error);
  }
}

getPost();

レスポンスの確認

fetch APIは、サーバーからレスポンスが返ってくれば成功とみなします:

async function getUser() {
  const response = await fetch('/api/users/123');

  // ステータスコードをチェック
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const user = await response.json();
  return user;
}

POSTリクエストでデータを送信

ユーザー作成の例

async function createUser(userData) {
  const response = await fetch('/api/users', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData)
  });

  if (!response.ok) {
    throw new Error(`作成に失敗しました: ${response.status}`);
  }

  const newUser = await response.json();
  return newUser;
}

// 使用例
const userData = {
  name: '田中太郎',
  email: 'tanaka@example.com'
};

createUser(userData)
  .then(user => console.log('作成されたユーザー:', user))
  .catch(error => console.error('エラー:', error));

フォームデータの送信

async function updateProfile(formData) {
  const response = await fetch('/api/profile', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: formData.get('name'),
      email: formData.get('email')
    })
  });

  return await response.json();
}

よく使うパターン

認証付きリクエスト

async function authenticatedRequest(url, options = {}) {
  const token = localStorage.getItem('authToken');

  const response = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
      ...options.headers,
    }
  });

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  return await response.json();
}

// 使用例
try {
  const userProfile = await authenticatedRequest('/api/profile');
  console.log(userProfile);
} catch (error) {
  console.error('認証エラーまたはリクエストエラー:', error);
}

クエリパラメータの追加

function buildURL(baseURL, params) {
  const url = new URL(baseURL);
  Object.keys(params).forEach(key =>
    url.searchParams.append(key, params[key])
  );
  return url.toString();
}

async function searchUsers(query) {
  const url = buildURL('/api/users', {
    search: query,
    page: 1,
    limit: 10
  });

  const response = await fetch(url);
  return await response.json();
}

// 使用例
const results = await searchUsers('田中');
// リクエスト先: /api/users?search=%E7%94%B0%E4%B8%AD&page=1&limit=10

エラーハンドリング

包括的なエラー処理

async function apiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
      },
      ...options,
    });

    // HTTPエラーをチェック
    if (!response.ok) {
      // サーバーからのエラーレスポンスを読み取り
      let errorMessage = `HTTP ${response.status}`;

      try {
        const errorData = await response.json();
        errorMessage = errorData.message || errorMessage;
      } catch {
        // JSON以外のエラーレスポンスの場合
        errorMessage = await response.text();
      }

      throw new Error(errorMessage);
    }

    // Content-Typeをチェックして適切にパース
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return await response.json();
    } else {
      return await response.text();
    }

  } catch (error) {
    // ネットワークエラーやその他の例外
    if (error instanceof TypeError) {
      throw new Error('ネットワークエラー: サーバーに接続できません');
    }
    throw error;
  }
}

実用的な使用例

// ユーザー情報を取得して画面に表示
async function displayUserInfo(userId) {
  try {
    const user = await apiRequest(`/api/users/${userId}`);

    // 画面更新
    document.getElementById('userName').textContent = user.name;
    document.getElementById('userEmail').textContent = user.email;

  } catch (error) {
    // エラーメッセージを表示
    document.getElementById('errorMessage').textContent =
      `ユーザー情報の取得に失敗しました: ${error.message}`;
  }
}

リクエストのキャンセル

長時間のリクエストをキャンセルできるようにしましょう:

async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  // AbortControllerでキャンセル可能にする
  const controller = new AbortController();

  // タイムアウト設定
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });

    clearTimeout(timeoutId);
    return response;

  } catch (error) {
    clearTimeout(timeoutId);

    if (error.name === 'AbortError') {
      throw new Error('リクエストがタイムアウトしました');
    }
    throw error;
  }
}

// 使用例
try {
  const response = await fetchWithTimeout('/api/slow-endpoint', {}, 3000);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error.message); // "リクエストがタイムアウトしました" など
}

実践的なAPI クライアントクラス

再利用しやすいAPIクライアントを作ってみましょう:

class APIClient {
  constructor(baseURL, defaultHeaders = {}) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      ...defaultHeaders
    };
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;

    const response = await fetch(url, {
      headers: {
        ...this.defaultHeaders,
        ...options.headers,
      },
      ...options,
    });

    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    return await response.json();
  }

  // 便利メソッド
  async get(endpoint, params = {}) {
    const url = new URL(`${this.baseURL}${endpoint}`);
    Object.keys(params).forEach(key =>
      url.searchParams.append(key, params[key])
    );

    return this.request(url.pathname + url.search);
  }

  async post(endpoint, data) {
    return this.request(endpoint, {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  async put(endpoint, data) {
    return this.request(endpoint, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }

  async delete(endpoint) {
    return this.request(endpoint, {
      method: 'DELETE',
    });
  }
}

// 使用例
const api = new APIClient('/api', {
  'Authorization': 'Bearer your-token-here'
});

// GET /api/users?page=1
const users = await api.get('/users', { page: 1 });

// POST /api/users
const newUser = await api.post('/users', {
  name: '佐藤花子',
  email: 'sato@example.com'
});

まとめ

fetch APIを使うことで、JavaScriptからAPIリクエストを簡単に送れるようになりました:

ポイント

  • fetch API: JavaScriptでHTTPリクエストを送るモダンな方法
  • async/await: Promise ベースで読みやすいコード
  • エラーハンドリング: response.ok でステータスをチェック
  • JSON処理: response.json() でデータを取得
  • 柔軟性: GET、POST、PUT、DELETE すべて対応
  • キャンセル: AbortController でリクエスト中断可能

次の記事では、Reactのコンポーネント内でfetch APIを使う方法について学んでいきましょう。useEffectフックと組み合わせて、コンポーネントのライフサイクルに合わせたAPI呼び出しを実装します。