はじめに#
Webアプリケーションを開発する際、サーバーからデータを取得したり、フォームの内容を送信したりする場面は数多くあります。このようなサーバーとの通信を行うために、JavaScriptでは「Fetch API」が標準的に使われています。
Fetch APIは、従来のXMLHttpRequestに代わる現代的なインターフェースです。Promiseベースで設計されているため、async/await構文と組み合わせることで、直感的で読みやすいコードを書くことができます。
本記事では、以下の内容を初心者向けにわかりやすく解説します。
- Fetch APIの基本概念と構文
- GETリクエストでデータを取得する方法
- POSTリクエストでデータを送信する方法
- レスポンスの処理方法
- エラーハンドリングのパターン
- 実践的な活用例
Fetch APIとは#
基本概念#
Fetch APIは、ネットワークを通じてリソースを取得するためのJavaScriptインターフェースです。fetch()関数を呼び出すことで、HTTPリクエストを送信し、サーバーからのレスポンスを受け取ることができます。
1
2
3
4
|
// Fetch APIの最もシンプルな使い方
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
|
fetch()はPromiseを返すため、async/awaitや.then()を使って非同期処理として扱います。
XMLHttpRequestとの違い#
Fetch APIが登場する以前は、XMLHttpRequest(XHR)がサーバー通信の主な手段でした。Fetch APIには以下のような利点があります。
| 項目 |
XMLHttpRequest |
Fetch API |
| 構文 |
コールバックベース |
Promiseベース |
| 可読性 |
ネストが深くなりやすい |
シンプルで読みやすい |
| ストリーム対応 |
なし |
あり |
| async/await対応 |
手動でPromise化が必要 |
ネイティブ対応 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// XMLHttpRequestの例(従来の方法)
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data");
xhr.onload = function() {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
}
};
xhr.send();
// Fetch APIの例(現代的な方法)
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
|
Fetch APIの方がコードがシンプルで、エラーハンドリングも統一的に行えます。
GETリクエストでデータを取得する#
基本的なGETリクエスト#
GETリクエストは、サーバーからデータを取得するための最も一般的なHTTPメソッドです。fetch()に対象のURLを渡すだけで、GETリクエストが送信されます。
1
2
3
4
5
6
7
|
async function fetchUsers() {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
console.log(users);
}
fetchUsers();
|
fetch()はデフォルトでGETメソッドを使用するため、明示的に指定する必要はありません。
クエリパラメータを付与する#
検索条件やページネーション情報など、追加のパラメータをURLに付与する場合は、URLSearchParamsを使うと便利です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
async function searchProducts(keyword, limit) {
// URLSearchParamsでクエリパラメータを構築
const params = new URLSearchParams({
q: keyword,
limit: limit
});
const url = `https://api.example.com/products?${params}`;
// 結果: https://api.example.com/products?q=laptop&limit=10
const response = await fetch(url);
const products = await response.json();
return products;
}
// 使用例
const results = await searchProducts("laptop", 10);
console.log(results);
|
URLSearchParamsは自動的にエンコード処理を行うため、特殊文字を含むパラメータも安全に扱えます。
レスポンスの取得と変換#
fetch()が返すResponseオブジェクトには、レスポンスデータを様々な形式で取得するためのメソッドが用意されています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
async function fetchData(url) {
const response = await fetch(url);
// JSON形式で取得
const jsonData = await response.json();
// テキスト形式で取得
// const textData = await response.text();
// Blob(バイナリデータ)として取得
// const blobData = await response.blob();
// ArrayBufferとして取得
// const bufferData = await response.arrayBuffer();
return jsonData;
}
|
用途に応じて適切なメソッドを選択してください。Web APIとの通信では、ほとんどの場合json()メソッドを使用します。
POSTリクエストでデータを送信する#
基本的なPOSTリクエスト#
POSTリクエストは、サーバーにデータを送信するために使用します。fetch()の第二引数にオプションオブジェクトを渡し、methodとbodyを指定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
async function createUser(userData) {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
const createdUser = await response.json();
return createdUser;
}
// 使用例
const newUser = await createUser({
name: "田中太郎",
email: "tanaka@example.com",
age: 25
});
console.log("作成されたユーザー:", newUser);
|
JSONデータを送信する場合は、Content-Typeヘッダーをapplication/jsonに設定し、bodyにはJSON.stringify()で変換した文字列を渡します。
フォームデータの送信#
HTMLフォームのデータを送信する場合は、FormDataオブジェクトを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
async function submitForm(formElement) {
// FormDataオブジェクトを作成
const formData = new FormData(formElement);
const response = await fetch("https://api.example.com/submit", {
method: "POST",
body: formData
// FormDataを使う場合、Content-Typeは自動設定される
});
return await response.json();
}
// HTML: <form id="myForm">...</form>
const form = document.getElementById("myForm");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const result = await submitForm(form);
console.log("送信結果:", result);
});
|
FormDataを使用する場合、Content-Typeヘッダーは自動的にmultipart/form-dataとして設定されるため、明示的に指定する必要はありません。
PUT・DELETE・PATCHリクエスト#
データの更新や削除には、PUT・DELETE・PATCHメソッドを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// PUTリクエスト: リソースの完全な更新
async function updateUser(userId, userData) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(userData)
});
return await response.json();
}
// PATCHリクエスト: リソースの部分的な更新
async function patchUser(userId, partialData) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(partialData)
});
return await response.json();
}
// DELETEリクエスト: リソースの削除
async function deleteUser(userId) {
const response = await fetch(`https://api.example.com/users/${userId}`, {
method: "DELETE"
});
return response.ok; // 削除成功でtrue
}
|
レスポンスの処理#
ステータスコードの確認#
fetch()は、ネットワークエラー以外ではPromiseを拒否しません。404や500などのHTTPエラーでも、Promiseは正常に解決されます。そのため、response.okやresponse.statusでステータスを確認することが重要です。
1
2
3
4
5
6
7
8
9
10
|
async function fetchWithStatusCheck(url) {
const response = await fetch(url);
// okプロパティ: ステータスコードが200-299の範囲ならtrue
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status}`);
}
return await response.json();
}
|
主要なステータスコードの意味は以下の通りです。
| ステータスコード |
意味 |
| 200 |
OK(成功) |
| 201 |
Created(リソース作成成功) |
| 400 |
Bad Request(リクエスト不正) |
| 401 |
Unauthorized(認証必要) |
| 403 |
Forbidden(アクセス禁止) |
| 404 |
Not Found(リソースなし) |
| 500 |
Internal Server Error(サーバーエラー) |
レスポンスヘッダーの取得#
レスポンスヘッダーはresponse.headersから取得できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
async function fetchWithHeaders(url) {
const response = await fetch(url);
// 特定のヘッダーを取得
const contentType = response.headers.get("Content-Type");
const contentLength = response.headers.get("Content-Length");
console.log("Content-Type:", contentType);
console.log("Content-Length:", contentLength);
// ヘッダーが期待する形式かチェック
if (!contentType || !contentType.includes("application/json")) {
throw new TypeError("レスポンスがJSON形式ではありません");
}
return await response.json();
}
|
エラーハンドリング#
try-catchによる基本的なエラーハンドリング#
fetch()のエラーハンドリングでは、ネットワークエラーとHTTPエラーを区別して処理することが重要です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
async function fetchData(url) {
try {
const response = await fetch(url);
// HTTPエラーのチェック
if (!response.ok) {
throw new Error(`HTTPエラー: ${response.status} ${response.statusText}`);
}
const data = await response.json();
return { success: true, data };
} catch (error) {
// ネットワークエラーまたはHTTPエラー
console.error("データ取得エラー:", error.message);
return { success: false, error: error.message };
}
}
// 使用例
const result = await fetchData("https://api.example.com/data");
if (result.success) {
console.log("取得データ:", result.data);
} else {
console.log("エラー発生:", result.error);
}
|
エラーの種類を判別する#
より細かいエラー処理が必要な場合は、エラーの種類を判別します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
async function fetchWithDetailedErrorHandling(url) {
try {
const response = await fetch(url);
if (!response.ok) {
// HTTPステータスに応じたエラー処理
switch (response.status) {
case 400:
throw new Error("リクエストが不正です。入力内容を確認してください。");
case 401:
throw new Error("認証が必要です。ログインしてください。");
case 403:
throw new Error("このリソースへのアクセス権限がありません。");
case 404:
throw new Error("指定されたリソースが見つかりません。");
case 500:
throw new Error("サーバーでエラーが発生しました。しばらくお待ちください。");
default:
throw new Error(`予期しないエラー: ${response.status}`);
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError) {
// ネットワークエラー(オフライン、DNS解決失敗など)
console.error("ネットワークエラー:", error.message);
} else {
// その他のエラー(HTTPエラー、JSONパースエラーなど)
console.error("エラー:", error.message);
}
throw error;
}
}
|
カスタムエラークラスの活用#
大規模なアプリケーションでは、カスタムエラークラスを定義すると管理しやすくなります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
// カスタムエラークラスの定義
class ApiError extends Error {
constructor(message, status, data = null) {
super(message);
this.name = "ApiError";
this.status = status;
this.data = data;
}
}
// APIクライアント関数
async function apiRequest(url, options = {}) {
try {
const response = await fetch(url, options);
// レスポンスボディを取得(エラー時も含む)
let data;
const contentType = response.headers.get("Content-Type");
if (contentType && contentType.includes("application/json")) {
data = await response.json();
} else {
data = await response.text();
}
if (!response.ok) {
throw new ApiError(
data.message || `HTTP ${response.status}`,
response.status,
data
);
}
return data;
} catch (error) {
if (error instanceof ApiError) {
throw error;
}
// ネットワークエラーなどをApiErrorでラップ
throw new ApiError(error.message, 0);
}
}
// 使用例
try {
const users = await apiRequest("https://api.example.com/users");
console.log(users);
} catch (error) {
if (error instanceof ApiError) {
console.log("ステータス:", error.status);
console.log("メッセージ:", error.message);
console.log("詳細データ:", error.data);
}
}
|
リクエストのカスタマイズ#
ヘッダーの設定#
認証トークンやカスタムヘッダーを設定する方法です。
1
2
3
4
5
6
7
8
9
10
11
12
|
async function fetchWithAuth(url, token) {
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
"X-Custom-Header": "custom-value"
}
});
return await response.json();
}
|
タイムアウトの設定#
AbortControllerを使用すると、リクエストにタイムアウトを設定できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === "AbortError") {
throw new Error("リクエストがタイムアウトしました");
}
throw error;
}
}
// 3秒でタイムアウトする設定
try {
const data = await fetchWithTimeout("https://api.example.com/data", 3000);
console.log(data);
} catch (error) {
console.error(error.message);
}
|
リクエストの中止#
ユーザー操作などでリクエストを中止する場合もAbortControllerを使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 検索機能での活用例
let currentController = null;
async function search(keyword) {
// 前回のリクエストがあればキャンセル
if (currentController) {
currentController.abort();
}
currentController = new AbortController();
try {
const response = await fetch(
`https://api.example.com/search?q=${encodeURIComponent(keyword)}`,
{ signal: currentController.signal }
);
if (!response.ok) {
throw new Error(`検索エラー: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
console.log("検索がキャンセルされました");
return null;
}
throw error;
}
}
|
実践的な活用例#
ユーザー一覧を取得して表示する#
実際のWebアプリケーションでよくあるパターンです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// APIからユーザー一覧を取得してDOMに表示する例
async function displayUsers() {
const container = document.getElementById("user-list");
container.innerHTML = "<p>読み込み中...</p>";
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("ユーザー情報の取得に失敗しました");
}
const users = await response.json();
// ユーザー一覧をHTMLとして構築
const html = users.map(user => `
<div class="user-card">
<h3>${user.name}</h3>
<p>メール: ${user.email}</p>
<p>会社: ${user.company.name}</p>
</div>
`).join("");
container.innerHTML = html;
} catch (error) {
container.innerHTML = `<p class="error">エラー: ${error.message}</p>`;
}
}
// ページ読み込み時に実行
displayUsers();
|
フォーム送信とバリデーション#
フォームデータの送信と結果の処理を行う例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// お問い合わせフォームの送信処理
const contactForm = document.getElementById("contact-form");
const submitButton = document.getElementById("submit-button");
const messageArea = document.getElementById("message");
contactForm.addEventListener("submit", async (e) => {
e.preventDefault();
// 送信中の状態を表示
submitButton.disabled = true;
submitButton.textContent = "送信中...";
messageArea.textContent = "";
try {
const formData = {
name: document.getElementById("name").value,
email: document.getElementById("email").value,
message: document.getElementById("message-input").value
};
const response = await fetch("https://api.example.com/contact", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(formData)
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || "送信に失敗しました");
}
// 送信成功
messageArea.textContent = "お問い合わせを受け付けました。";
messageArea.className = "success";
contactForm.reset();
} catch (error) {
// エラー表示
messageArea.textContent = error.message;
messageArea.className = "error";
} finally {
// ボタンを元に戻す
submitButton.disabled = false;
submitButton.textContent = "送信";
}
});
|
再利用可能なAPIクライアントの作成#
アプリケーション全体で使いまわせるAPIクライアントを作成する例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
// apiClient.js
const BASE_URL = "https://api.example.com";
const apiClient = {
// 共通のfetch処理
async request(endpoint, options = {}) {
const url = `${BASE_URL}${endpoint}`;
const config = {
headers: {
"Content-Type": "application/json",
...options.headers
},
...options
};
// 認証トークンがあれば追加
const token = localStorage.getItem("authToken");
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch(url, config);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}`);
}
return response.json();
},
// GETリクエスト
get(endpoint) {
return this.request(endpoint);
},
// POSTリクエスト
post(endpoint, data) {
return this.request(endpoint, {
method: "POST",
body: JSON.stringify(data)
});
},
// PUTリクエスト
put(endpoint, data) {
return this.request(endpoint, {
method: "PUT",
body: JSON.stringify(data)
});
},
// DELETEリクエスト
delete(endpoint) {
return this.request(endpoint, {
method: "DELETE"
});
}
};
// 使用例
async function main() {
try {
// ユーザー一覧を取得
const users = await apiClient.get("/users");
console.log(users);
// 新規ユーザーを作成
const newUser = await apiClient.post("/users", {
name: "新規ユーザー",
email: "new@example.com"
});
console.log(newUser);
// ユーザー情報を更新
const updatedUser = await apiClient.put("/users/1", {
name: "更新されたユーザー"
});
console.log(updatedUser);
// ユーザーを削除
await apiClient.delete("/users/1");
console.log("削除完了");
} catch (error) {
console.error("APIエラー:", error.message);
}
}
|
Fetch APIの処理フロー#
Fetch APIを使った通信の全体的な流れを図で表すと以下のようになります。
flowchart TD
A[fetch関数を呼び出す] --> B[リクエスト送信]
B --> C{ネットワーク<br/>エラー?}
C -->|はい| D[Promiseが拒否<br/>TypeErrorが発生]
C -->|いいえ| E[Responseオブジェクトを受信]
E --> F{response.ok<br/>をチェック}
F -->|true<br/>200-299| G[レスポンスボディを取得<br/>json / text / blob]
F -->|false<br/>400-599| H[エラー処理<br/>ステータスに応じた対応]
G --> I[データを処理して完了]
H --> J[エラーをthrowまたは<br/>エラーメッセージ表示]
D --> K[catchブロックで処理]まとめ#
本記事では、JavaScriptのFetch APIを使ったWeb API通信の基本を解説しました。
学習したポイントをまとめます。
- Fetch APIの基本:
fetch()関数はPromiseを返し、async/awaitと組み合わせて直感的に使える
- GETリクエスト: URLを渡すだけでデータ取得が可能、クエリパラメータはURLSearchParamsで構築
- POSTリクエスト: methodとbodyオプションを指定し、Content-Typeヘッダーを適切に設定
- レスポンス処理:
response.okでステータスを確認し、json()などのメソッドでデータを取得
- エラーハンドリング: ネットワークエラーとHTTPエラーを区別して処理することが重要
- リクエストのカスタマイズ: AbortControllerでタイムアウトやキャンセルを実装できる
Fetch APIはモダンなWebアプリケーション開発に欠かせない技術です。本記事で紹介したパターンを参考に、実際のプロジェクトでWeb API通信を実装してみてください。
参考リンク#