Day 65:検索APIアプリ②
Day 65では、第5フェーズ「非同期処理とAPI」の続きとして、前回作成したユーザー検索アプリに機能追加を行います。今回追加するのは、ローディング、エラー処理、検索結果なし表示の3つです。Day 64では、キーワード入力、API検索、結果表示という検索アプリの基本形を作成しました。そこでは、入力欄からキーワードを受け取り、公開APIから取得したユーザーデータを filter() で絞り込み、結果を一覧表示する流れを学びました。Day 65では、その基本形を「より使いやすく、より実用的なアプリ」へ育てていきます。
初心者の方にとって、このDay 65はとても大切です。なぜなら、ここで学ぶ内容は、見た目の追加機能のように見えて、実はアプリの完成度を大きく左右するからです。検索機能そのものが動くだけでは、まだ実用的とは言えません。たとえば、検索ボタンを押してもすぐに何も変わらなければ、使う人は不安になります。通信に失敗したのに何も案内がなければ、壊れていると思われるかもしれません。検索結果が0件だったときに何も表示されなければ、検索できたのかどうかもわかりません。こうした「成功しなかったとき」や「待っている途中」の体験まで考えることが、アプリ開発ではとても重要です。
つまりDay 65は、「検索機能を作る回」ではなく、「検索アプリを完成に近づける回」です。ここでは、Day 62で学んだローディング処理、Day 63で学んだAPIエラー処理、そしてDay 64で扱った検索結果表示を、1つのアプリの中で整理して統合していきます。初心者の方は、「何を追加するか」だけでなく、「なぜそれが必要なのか」「どのタイミングで処理すべきか」「どんな状態をユーザーへ伝えるべきか」という設計の考え方まで意識しながら進めると、非常に力がつきます。今回も、プログラミング初心者向けに、ステップバイステップで、例題・コード・テンプレートを交えながら丁寧に解説していきます。
まず最初に結論をつかむ
最初に今回の内容を一言でまとめると、Day 65では「検索中であること」「検索に失敗したこと」「検索結果が0件であること」を、画面でわかるようにすることが目標です。
今回追加する機能を整理すると、次の通りです。
- ローディング:検索中であることを表示する
- エラー処理:通信失敗時に案内を出す
- 検索結果なし表示:該当データがないことを伝える
初心者の方は、これらを全部まとめて「状態を伝える機能」と考えるとわかりやすいです。検索アプリでは、結果だけでなく、途中状態や失敗状態も画面に伝える必要があります。
なぜこの3つの機能追加が重要なのか
ここはDay 65の中心です。なぜローディング、エラー処理、検索結果なし表示が大切なのかというと、検索アプリには「成功」以外にもいくつもの状態があるからです。
たとえば、検索アプリには少なくとも次の状態があります。
- まだ何も検索していない状態
- 検索中の状態
- 検索成功で結果がある状態
- 検索成功だが結果が0件の状態
- 通信失敗で検索できなかった状態
初心者の方は、アプリは「結果を出すだけのもの」ではなく、「今どんな状態かを伝えるもの」でもあると理解すると、設計の視点が大きく広がります。
Day 64の基本アプリを振り返る
Day 64では、ユーザー検索アプリの基本形を作りました。そこでは、次の流れがありました。
- 入力欄からキーワードを取得する
- APIからユーザー一覧を取得する
- `filter()` で条件に合うユーザーを絞り込む
- 結果を一覧表示する
この流れ自体はすでに検索アプリとして成立しています。しかし、まだ次のような問題が残っていました。
- 検索中に何が起きているかわかりにくい
- 通信に失敗したときの案内が弱い
- 結果0件のときに表示があいまいになりやすい
Day 65では、この「基本機能はあるが、体験としてはまだ不親切」な状態を改善していきます。
最初に完成イメージを持つ
コードに入る前に、今回の完成イメージを言葉で整理しておきます。理想的には、次のような流れになると使いやすいです。
- ユーザーがキーワードを入力して検索ボタンを押す
- すぐに「検索中です…」と表示される
- 通信が成功したら、結果を一覧表示する
- 該当ユーザーがいなければ「見つかりませんでした」と表示する
- 通信に失敗したら「検索に失敗しました」と表示する
- 検索が終わったらローディング表示を消す
初心者の方は、完成コードを見る前にこの流れを日本語で説明できるようにしておくと、JavaScriptで何を書くべきかが見えやすくなります。
ローディング機能を追加する意味
まずはローディングです。ローディングは、検索ボタンを押してから結果が出るまでの間、「今ちゃんと処理中です」と伝える役割があります。検索アプリでは、押した瞬間に必ず結果が出るとは限りません。API通信には待ち時間があるからです。
そのため、検索開始時に何も変化がないと、ユーザーは次のように感じやすくなります。
- ボタンが押せていないのではないか
- 検索機能が壊れているのではないか
- もう一度押したほうがよいのではないか
ローディング表示は、この不安を減らすための大切な案内です。初心者の方は、ローディングを「飾り」ではなく、「処理中を伝えるUI」と考えると理解しやすいです。
ローディング表示の基本形
まずHTMLにローディング用の要素を置きます。
<p id="loading" style="display: none;">検索中です...</p>
最初は display: none; にして見えない状態にしておきます。そして検索開始時に表示し、終了後に非表示へ戻します。
loading.style.display = "block";
loading.style.display = "none";JavaScript初心者の方は、ここで「最初から表示するのではなく、必要なときだけ出す」という考え方を意識するとよいです。
どのタイミングでローディングを出すのか
ここは重要です。ローディング表示は、fetch() の前に出す必要があります。もし取得処理が始まったあとに表示しようとすると、タイミングとして遅くなります。
正しい流れはこうです。
- 入力チェックを終える
- 検索開始と同時にローディングを表示する
- API通信を行う
- 成功でも失敗でも最後にローディングを消す
初心者の方は、「ローディングは検索が始まる前に出し、検索が完全に終わってから消す」と覚えると整理しやすいです。
エラー処理を追加する意味
次にエラー処理です。検索アプリでは、API通信が失敗することがあります。ネットワークの問題、URLの問題、サーバー側の不調など、原因はいろいろあります。大切なのは、失敗そのものをゼロにすることではなく、「失敗したときにどう見せるか」を設計することです。
エラー処理がないと、ユーザーから見ると「何も表示されない」「止まったように見える」だけになりやすいです。それでは非常に不親切です。初心者の方は、失敗したときこそメッセージ表示が大事だと理解すると、より実用的なアプリづくりに近づきます。
try / catch を検索アプリに組み込む
Day 63で学んだ try / catch を使います。たとえば、次のような形です。
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("サーバーエラーが発生しました。");
}
const users = await response.json();
} catch (error) {
message.textContent = "検索に失敗しました。時間をおいて再試行してください。";
console.log(error);
}JavaScriptこのようにすると、失敗したときに画面へ案内を出しつつ、開発者向けには詳細をコンソールで確認できます。初心者の方は、「画面に見せるメッセージ」と「自分が確認する情報」は役割が違うと理解すると整理しやすいです。
検索結果なし表示の意味
ここも非常に大事です。検索結果が0件というのは、エラーではありません。通信は成功していて、検索も実行されています。ただ、「条件に合うデータがなかった」というだけです。この状態をきちんと区別して表示することが重要です。
初心者の方は、0件とエラーを同じように考えがちですが、実は意味がまったく違います。
- 0件:検索は正常にできたが、該当ユーザーがいない
- エラー:検索処理自体が正常に完了していない
この違いを意識できると、画面メッセージの設計がぐっとよくなります。
検索結果なし表示の基本形
検索結果配列の長さを見て、0件なら専用メッセージを出します。
if (results.length === 0) {
message.textContent = "該当するユーザーが見つかりませんでした。";
return;
}JavaScriptこの1行があるだけで、ユーザーは「検索は動いたが結果がなかった」と理解できます。初心者の方は、「何も表示しない」ことは、ユーザーにとって親切ではない場面が多いと覚えておくとよいです。
空欄チェック・0件・エラーは別物
ここはかなり重要なので深掘りします。検索アプリでは、似て見えて役割の違う状態がいくつもあります。
- 空欄チェック:そもそも検索条件が入力されていない
- 検索結果0件:条件はあるが該当データがない
- 通信エラー:検索そのものが正常にできていない
この3つはすべて別です。そのため、メッセージも分けるべきです。たとえば、
- 空欄なら「キーワードを入力してください。」
- 0件なら「該当するユーザーが見つかりませんでした。」
- エラーなら「検索に失敗しました。時間をおいて再試行してください。」
初心者の方は、検索アプリでは「失敗」と一言でまとめず、状態ごとに丁寧に分けることが大切だと理解すると実践的です。
メッセージ表示欄を状態案内として使う
検索アプリでは、メッセージ欄がとても重要です。ここに現在の状態を表示することで、ユーザーは何が起きているかを理解しやすくなります。
たとえばメッセージ欄には、次のような内容が入ります。
- 検索前:「キーワードを入力して検索してください。」
- 検索中:「ユーザーを検索中です…」
- 成功:「3件のユーザーが見つかりました。」
- 0件:「該当するユーザーが見つかりませんでした。」
- 空欄:「キーワードを入力してください。」
- エラー:「検索に失敗しました。時間をおいて再試行してください。」
初心者の方は、メッセージ欄を単なる補助ではなく、「アプリの状態を伝える中心」と考えると設計しやすくなります。
完成コードの流れを先に言葉で整理する
ここで、完成版の流れをもう一度日本語で整理します。
- ボタンが押されたら入力値を取得する
- 空欄ならエラーメッセージを出して終了する
- 前回の結果を消す
- ローディング表示を出し、検索中メッセージを表示する
- APIからユーザー一覧を取得する
- 取得に成功したら `filter()` で絞り込む
- 0件なら専用メッセージを出す
- 結果があれば一覧表示する
- もし通信失敗ならエラーメッセージを出す
- 最後にローディング表示を消す
初心者の方は、この流れを理解してからコードを見ると、処理の場所ごとの意味がかなり見えやすくなります。
そのまま使える完成テンプレート
ここでは、Day 65の内容をまとめた完成テンプレートを紹介します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Day 65 検索APIアプリ②</title>
<style>
/* ローディング表示 */
#loading {
display: none;
color: blue;
font-weight: bold;
}
/* エラーメッセージ用 */
.error {
color: red;
}
/* 結果リストの余白 */
li {
margin-bottom: 8px;
}
</style>
</head>
<body>
<h2>ユーザー検索アプリ</h2>
<input type="text" id="keywordInput" placeholder="名前を入力してください">
<button id="searchButton">検索</button>
<p id="loading">検索中です...</p>
<p id="message">キーワードを入力して検索してください。</p>
<ul id="resultList"></ul>
<script>
// 必要な要素を取得する
const keywordInput = document.getElementById("keywordInput");
const searchButton = document.getElementById("searchButton");
const loading = document.getElementById("loading");
const message = document.getElementById("message");
const resultList = document.getElementById("resultList");
// ユーザー検索を行う関数
async function searchUsers() {
// 入力値を取得する
const keyword = keywordInput.value.trim();
// 前回の検索結果を空にする
resultList.innerHTML = "";
// メッセージの見た目を初期化する
message.classList.remove("error");
// 空欄なら検索しない
if (keyword === "") {
message.textContent = "キーワードを入力してください。";
message.classList.add("error");
return;
}
try {
// ローディング表示を出す
loading.style.display = "block";
// 検索中メッセージを表示する
message.textContent = "ユーザーを検索中です...";
// APIからユーザー一覧を取得する
const response = await fetch("https://jsonplaceholder.typicode.com/users");
// HTTPエラーなら失敗として扱う
if (!response.ok) {
throw new Error("サーバーエラーが発生しました。");
}
// JSONデータを読み取る
const users = await response.json();
// 名前にキーワードを含むユーザーだけを絞り込む
const results = users.filter(function (user) {
return user.name.toLowerCase().includes(keyword.toLowerCase());
});
// 検索結果が0件ならメッセージを表示する
if (results.length === 0) {
message.textContent = "該当するユーザーが見つかりませんでした。";
return;
}
// 検索成功メッセージ
message.textContent = `${results.length}件のユーザーが見つかりました。`;
// 検索結果を画面に表示する
results.forEach(function (user) {
const li = document.createElement("li");
// 名前とメールアドレスを表示する
li.textContent = `${user.name} / ${user.email}`;
resultList.appendChild(li);
});
} catch (error) {
// 通信失敗時のメッセージ
message.textContent = "検索に失敗しました。時間をおいて再試行してください。";
message.classList.add("error");
// 開発者向けにエラー詳細を出力する
console.log("エラー詳細:", error);
}
// 成功でも失敗でもローディング表示を消す
loading.style.display = "none";
}
// 検索ボタンが押されたら検索を実行する
searchButton.addEventListener("click", function () {
searchUsers();
});
</script>
</body>
</html>
この完成コードの重要な見どころ
初心者の方は、完成コードの中で特に次の部分を意識すると理解が深まりやすいです。
- 空欄チェックが最初にあること
- 検索開始前に `resultList.innerHTML = “”` で前回結果を消していること
- ローディング表示を `fetch()` の前に出していること
- `response.ok` でHTTPエラー確認もしていること
- 検索結果0件と通信エラーを別メッセージにしていること
- 最後にローディング表示を必ず消していること
これらは単なる書き方ではなく、「使いやすい検索アプリにするための設計」です。
特に深掘りしたい重要ポイント
今回特に大切なのは、「0件」と「エラー」をきちんと分けて扱っている点です。初心者の方は、どちらも「うまく結果が出なかった」という意味では似て見えるかもしれません。しかし実際には、0件は検索が成功している状態であり、エラーは検索処理自体が正常に完了していない状態です。この違いを正しく扱えるようになると、アプリの状態管理が一段階レベルアップします。
また、ローディング表示を最後に必ず消すことも非常に重要です。途中で return する分岐があるときは特に注意が必要です。初心者の方は、「表示を出す処理」と「戻す処理」は必ずセットで考える習慣を持つと、UI不具合が減りやすくなります。
情報セキュリティの観点での注意
今回の検索アプリでも、外部APIから取得したデータを画面へ表示しています。そのため、表示方法には引き続き注意が必要です。学習段階では信頼できる練習用APIを使う前提ですが、外部データを扱う以上、「そのまま画面へ埋め込む内容」には慎重であるべきです。まずは textContent を使った安全な表示を基本にするとよいです。また、エラー時に内部情報をそのままユーザー画面へ出さないようにすることも大切です。初心者の方は、「便利さ」と「安全さ」を両立する意識を少しずつ持つと、堅実な開発習慣につながります。
初心者が特に注意したいポイント
Day 65では、次の点を特に意識すると理解が深まりやすいです。
- ローディング表示は検索開始前に出します。
- 成功でも失敗でも最後にローディングを消します。
- 検索結果0件と通信エラーは別の状態です。
- メッセージ欄は現在の状態を伝える重要な場所です。
- 空欄チェック、0件表示、エラー表示はそれぞれ役割が違います。
特に重要なのは、「ただ検索できる」だけでなく、「どんな状態でもユーザーが迷わない」ようにすることです。
Day 65のゴールを整理する
Day 65では、次のことができるようになっていれば十分です。
- 検索アプリにローディング表示を追加できる
- 通信失敗時のエラー処理を実装できる
- 検索結果0件時に専用メッセージを表示できる
- 空欄、0件、エラーの違いを説明できる
- 検索アプリの状態をメッセージ欄で案内できる
ここまでできれば、Day 65としてはとても順調です。検索機能そのものだけでなく、ユーザー体験まで含めて考えられるようになっています。
まとめ
Day 65では、第5フェーズ「非同期処理とAPI」の続きとして、ユーザー検索アプリにローディング、エラー処理、検索結果なし表示を追加しました。これによって、検索アプリは「キーワードを入れて結果を出す」だけでなく、「今検索中であること」「検索に失敗したこと」「該当するデータがなかったこと」まで丁寧に伝えられるようになります。技術的には、ローディング表示の切り替え、try / catch による通信失敗対応、results.length === 0 による0件分岐、メッセージ欄による状態案内など、これまで学んだ要素が統合されています。
特に大切なのは、空欄、0件、エラーがすべて別の状態であり、それぞれに合ったメッセージや処理が必要だという点です。この違いをきちんと扱えるようになると、アプリは単に動くだけでなく、使う人にとってわかりやすく信頼しやすいものへ近づきます。
次に進む前に、今回のテンプレートを実際に動かし、空欄検索、ヒットする検索、0件検索、URLをわざと変えたエラー検索の4パターンを試してみるのがおすすめです。そして、「それぞれの状態で何を表示すべきか」を自分の言葉で説明できるようになると、Day 65の理解がかなり深まっています。
