基礎から学ぶJavaScript入門 90日コース | 非同期処理とAPI - Day 70:API総合制作

JavaScript 90日で身につけるJavaScript
スポンサーリンク
スポンサーリンク

Day 70:API総合制作

Day 70では、第5フェーズ「非同期処理とAPI」の総仕上げとして、APIを使った総合制作に取り組みます。今回の制作テーマは、天気アプリ、ニュース検索アプリ、書籍検索アプリ、レシピ検索アプリ、画像検索アプリ の中から1つ選んで作ることです。今回はその中から、初心者の方にも取り組みやすく、これまで学んだ内容をバランスよく活かせる題材として、書籍検索アプリ を選んで解説します。

なぜ書籍検索アプリを選ぶのかというと、学習としてちょうどよい難易度だからです。天気アプリは位置情報やAPIキーが関わることが多く、ニュースや画像検索もAPIキーや利用制限の壁にぶつかりやすいことがあります。レシピ検索もサービスによっては認証や複雑なレスポンス構造があります。それに対して書籍検索は、比較的扱いやすい公開APIがあり、検索という操作もすでにDay 64からDay 69で学んできた流れとつながっています。そのため、総合制作として「今までの学びをしっかり形にする」という目的にとても向いています。

今回のDay 70では、単に検索できるだけの簡単なものではなく、これまで学んだ内容をできるだけ自然に取り入れます。具体的には、入力欄によるキーワード検索、API取得、ローディング表示、エラー処理、検索結果なし表示、カード形式の表示、コード整理まで含めて、ひとつのまとまったアプリとして仕上げていきます。初心者の方は、ここで「単発の練習」から「完成したアプリらしいもの」へ進む感覚をつかめます。今回も、プログラミング初心者向けに、ステップバイステップで、例題やコード例、テンプレートを交えながら丁寧に解説していきます。

まず最初に結論をつかむ

今回作る書籍検索アプリは、入力欄にキーワードを入れて検索し、APIから書籍データを取得して、結果を画面へ一覧表示するアプリです。

このアプリの基本的な流れは次の通りです。

  • 検索キーワードを入力する
  • 検索ボタンを押す
  • ローディング表示を出す
  • APIから書籍データを取得する
  • 検索結果をカード形式で表示する
  • 0件なら専用メッセージを表示する
  • 失敗したらエラーメッセージを表示する
  • 最後にローディング表示を消す

初心者の方は、まずこの流れを頭に入れてからコードを見ると、全体像がかなりつかみやすくなります。

今回使うAPIについて

今回の書籍検索アプリでは、Google Books API の公開エンドポイントを使う形で学習します。検索語をURLに含めることで、該当する書籍情報をJSON形式で取得できます。たとえば、JavaScript で検索するURLは次のような形です。

https://www.googleapis.com/books/v1/volumes?q=JavaScript
JavaScript

このAPIから返ってくるデータには、書籍タイトル、著者名、出版社、説明文、画像URLなどが含まれることがあります。ただし、すべてのデータに同じ項目がそろっているわけではありません。ここは初心者の方にとってとても大切な注意点です。APIデータは、自分で用意したオブジェクトとは違い、項目が欠けていることがあります。そのため、「もしこの情報がなかったらどう表示するか」を考える必要があります。

なぜ書籍検索アプリが総合制作に向いているのか

書籍検索アプリは、これまで学んだ内容が自然にまとまる題材です。たとえば、次の要素がすべて入っています。

  • 入力欄から値を取得する
  • 検索ボタンのイベントを処理する
  • `fetch()` と `async / await` でAPI通信する
  • ローディング表示を切り替える
  • 検索結果0件を扱う
  • エラー処理を行う
  • カード形式で結果を見やすく表示する
  • コードを関数に分けて整理する

初心者の方は、書籍検索アプリを通して、「APIを使ったアプリで必要な要素が一通り入っている」と理解すると、総復習として非常に価値があります。

まずは完成イメージを持つ

コードに入る前に、完成イメージを言葉で整理しておきます。今回のアプリは、たとえば次のように動きます。

  • 入力欄に `JavaScript` と入力する
  • 検索ボタンを押す
  • 「検索中です…」と表示される
  • 書籍カードが複数件表示される
  • 各カードにはタイトル、著者、出版社、説明文の一部が表示される
  • 何も見つからなければ「該当する書籍が見つかりませんでした」と表示される
  • 通信に失敗したら「検索に失敗しました」と表示される

初心者の方は、このように日本語でアプリの動きを説明できるようになってからコードに入ると、処理の目的が見えやすくなります。

ステップ1:HTMLの部品を用意する

まずはアプリに必要なHTMLを用意します。今回は次の部品が必要です。

  • 検索キーワード入力欄
  • 検索ボタン
  • ローディング表示
  • メッセージ表示欄
  • 検索結果表示エリア

基本のHTMLは次のようになります。

<h2>書籍検索アプリ</h2>

<input type="text" id="keywordInput" placeholder="書籍名やキーワードを入力">
<button id="searchButton">検索</button>

<p id="loading">検索中です...</p>
<p id="message">キーワードを入力して検索してください。</p>
<div id="resultArea"></div>

初心者の方は、「入力する場所」「検索を開始する場所」「状態を伝える場所」「結果を出す場所」をはっきり分けると、その後のJavaScriptが整理しやすくなります。

ステップ2:まずは入力値を取得する

検索アプリなので、最初に入力欄の値を受け取る必要があります。

const keywordInput = document.getElementById("keywordInput");
const searchButton = document.getElementById("searchButton");

searchButton.addEventListener("click", function () {
  const keyword = keywordInput.value.trim();
  console.log(keyword);
});
JavaScript

ここでは trim() を使って前後の空白を除いています。初心者の方は、この時点で console.log() を使って本当に入力値が取れているか確認すると安心です。

ステップ3:空欄チェックを入れる

検索キーワードが空のまま検索しても意味がありません。そのため、空欄チェックを入れます。

if (keyword === "") {
  message.textContent = "キーワードを入力してください。";
  return;
}
JavaScript

初心者の方は、入力アプリでは「正しい入力がある前提」で進めないことが大切です。まず入力不足への対応を入れておくと、アプリが安定しやすくなります。

ステップ4:APIリクエスト用のURLを作る

次に、入力されたキーワードを使ってAPIのURLを作ります。ここで注意したいのは、キーワードの中に空白や記号が入る可能性があることです。そのため、encodeURIComponent() を使います。

const url = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(keyword)}`;
JavaScript

ここはとても重要です。初心者の方は、ユーザー入力をそのままURLにくっつけるのではなく、安全にURL用へ変換する必要があると覚えておくとよいです。

ステップ5:APIからデータを取得する

次に fetch() と async / await を使って書籍データを取得します。

async function searchBooks(keyword) {
  const url = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(keyword)}`;
  const response = await fetch(url);
  const data = await response.json();

  console.log(data);
}
JavaScript

初心者の方は、ここでまず console.log(data) を見て、どんな形のデータが返ってくるかを確認することが大切です。API学習では、いきなり表示を作るのではなく、中身を観察する習慣が非常に重要です。

返ってくるデータの構造を理解する

Google Books API では、検索結果が items という配列に入っていることが多いです。その中の各要素に volumeInfo という情報があり、その中にタイトルや著者名があります。つまり、使いたいデータはこのような場所にあります。

  • タイトル:`book.volumeInfo.title`
  • 著者:`book.volumeInfo.authors`
  • 出版社:`book.volumeInfo.publisher`
  • 説明:`book.volumeInfo.description`

初心者の方は、APIのデータ構造が少し深くても慌てず、「まず全体を見て、どこに欲しい情報があるかを確認する」ことが大切です。

ステップ6:検索結果をカード形式で表示する

取得した書籍データを画面へ表示します。今回はカード形式にします。カード形式は、タイトル・著者・出版社などを1冊ずつまとめて見せるのに向いています。

function renderBooks(books) {
  resultArea.innerHTML = "";

  books.forEach(function (book) {
    const info = book.volumeInfo;

    const card = document.createElement("div");

    const title = document.createElement("h3");
    title.textContent = info.title || "タイトル不明";

    const authors = document.createElement("p");
    authors.textContent = "著者: " + (info.authors ? info.authors.join(", ") : "不明");

    const publisher = document.createElement("p");
    publisher.textContent = "出版社: " + (info.publisher || "不明");

    card.appendChild(title);
    card.appendChild(authors);
    card.appendChild(publisher);

    resultArea.appendChild(card);
  });
}
JavaScript

このコードで大切なのは、|| "不明" や条件演算子を使って、データがない場合の表示も考えている点です。初心者の方は、APIの値は必ずあるとは限らないと意識しておくことが重要です。

なぜデータがない場合の表示が必要なのか

ここはかなり大切です。自分で作る配列なら、必要な項目を全部そろえられます。しかしAPIデータは外部から来るため、書籍によって著者情報がなかったり、出版社が入っていなかったりします。そのときに何も考えず表示しようとすると、undefined が見えてしまうことがあります。

初心者の方は、「データが欠けていても壊れない表示」を作ることが、外部データを扱うアプリではとても大切だと覚えておくとよいです。

ステップ7:ローディング処理を追加する

検索には少し時間がかかることがあるため、ローディング表示を追加します。

loading.style.display = "block";
JavaScript

検索が終わったら消します。

loading.style.display = "none";
JavaScript

初心者の方は、ローディング表示は「通信が始まる前に出して、終わったら消す」という基本をしっかり守ることが大切です。

ステップ8:エラー処理を追加する

次に try...catch を使ってエラー処理を入れます。

try {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("通信に失敗しました。");
  }

  const data = await response.json();
} catch (error) {
  message.textContent = "検索に失敗しました。時間をおいて再試行してください。";
  console.log(error);
}
JavaScript

初心者の方は、エラー処理では「ユーザー向けメッセージ」と「開発者向け確認情報」を分けることが大切です。画面にはやさしい案内、コンソールには詳細を出すと整理しやすいです。

ステップ9:検索結果なし表示を追加する

書籍検索では、結果が0件になることもあります。その場合は専用メッセージを出します。

if (!data.items || data.items.length === 0) {
  message.textContent = "該当する書籍が見つかりませんでした。";
  resultArea.innerHTML = "";
  return;
}
JavaScript

初心者の方は、0件はエラーではなく正常な検索結果の1つだと理解しておくことが大切です。

ステップ10:コードを整理する

総合制作では、コード整理も重要です。今回は次のように役割分担するとわかりやすいです。

  • `setMessage()`:メッセージ表示担当
  • `renderBooks()`:検索結果表示担当
  • `searchBooks()`:API検索担当

このように分けると、読みやすく、修正しやすくなります。初心者の方は、「関数ごとに仕事を1つずつ持たせる」意識をここで育てると、今後の開発でも役立ちます。

そのまま使える完成テンプレート

ここでは、Day 70の総合制作として、そのまま使える書籍検索アプリの完成テンプレートを紹介します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Day 70 API総合制作 書籍検索アプリ</title>
  <style>
    body {
      font-family: sans-serif;
      max-width: 900px;
      margin: 24px auto;
      padding: 16px;
    }

    input, button {
      padding: 8px;
      margin-right: 8px;
      margin-bottom: 12px;
    }

    #loading {
      display: none;
      color: blue;
      font-weight: bold;
    }

    .error {
      color: red;
    }

    .success {
      color: green;
    }

    .book-card {
      border: 1px solid #ccc;
      border-radius: 8px;
      padding: 12px;
      margin-bottom: 12px;
      background-color: #f9f9f9;
    }
  </style>
</head>
<body>
  <h2>書籍検索アプリ</h2>

  <input type="text" id="keywordInput" placeholder="書籍名やキーワードを入力">
  <button id="searchButton">検索</button>

  <p id="loading">検索中です...</p>
  <p id="message">キーワードを入力して検索してください。</p>
  <div id="resultArea"></div>

  <script>
    // 必要な要素を取得する
    const keywordInput = document.getElementById("keywordInput");
    const searchButton = document.getElementById("searchButton");
    const loading = document.getElementById("loading");
    const message = document.getElementById("message");
    const resultArea = document.getElementById("resultArea");

    // メッセージ表示を担当する関数
    function setMessage(text, className = "") {
      message.textContent = text;
      message.className = className;
    }

    // 書籍一覧をカード形式で表示する関数
    function renderBooks(books) {
      // 前回の表示を空にする
      resultArea.innerHTML = "";

      // 1冊ずつカードを作る
      books.forEach(function (book) {
        const info = book.volumeInfo;

        const card = document.createElement("div");
        card.classList.add("book-card");

        // タイトルを表示する
        const title = document.createElement("h3");
        title.textContent = info.title || "タイトル不明";

        // 著者を表示する
        const authors = document.createElement("p");
        authors.textContent = "著者: " + (info.authors ? info.authors.join(", ") : "不明");

        // 出版社を表示する
        const publisher = document.createElement("p");
        publisher.textContent = "出版社: " + (info.publisher || "不明");

        // 説明文を表示する
        const description = document.createElement("p");
        description.textContent = "説明: " + (info.description ? info.description.slice(0, 100) + "..." : "説明なし");

        // カードに追加する
        card.appendChild(title);
        card.appendChild(authors);
        card.appendChild(publisher);
        card.appendChild(description);

        // 結果エリアに追加する
        resultArea.appendChild(card);
      });
    }

    // 書籍を検索する関数
    async function searchBooks() {
      // 入力値を取得する
      const keyword = keywordInput.value.trim();

      // 空欄チェック
      if (keyword === "") {
        setMessage("キーワードを入力してください。", "error");
        resultArea.innerHTML = "";
        return;
      }

      try {
        // ローディング表示を出す
        loading.style.display = "block";

        // メッセージを更新する
        setMessage("書籍を検索中です...");

        // 前回の結果を空にする
        resultArea.innerHTML = "";

        // APIのURLを作る
        const url = `https://www.googleapis.com/books/v1/volumes?q=${encodeURIComponent(keyword)}`;

        // APIへリクエストを送る
        const response = await fetch(url);

        // HTTPエラーを確認する
        if (!response.ok) {
          throw new Error("書籍データの取得に失敗しました。");
        }

        // JSONデータを読み取る
        const data = await response.json();

        // 検索結果が0件ならメッセージを表示する
        if (!data.items || data.items.length === 0) {
          setMessage("該当する書籍が見つかりませんでした。");
          return;
        }

        // 書籍一覧を表示する
        renderBooks(data.items);

        // 件数メッセージを表示する
        setMessage(`${data.items.length}件の書籍が見つかりました。`, "success");
      } catch (error) {
        // エラー時の表示
        setMessage("検索に失敗しました。時間をおいて再試行してください。", "error");
        resultArea.innerHTML = "";

        // 開発者向けに詳細を表示する
        console.log("エラー詳細:", error);
      }

      // ローディング表示を消す
      loading.style.display = "none";
    }

    // ボタンが押されたら検索を実行する
    searchButton.addEventListener("click", function () {
      searchBooks();
    });
  </script>
</body>
</html>

この完成コードの見どころ

初心者の方は、完成コードの中で次の点に特に注目すると理解が深まりやすいです。

  • 入力値を `trim()` で整えていること
  • `encodeURIComponent()` で検索語をURL向けに安全に変換していること
  • `renderBooks()` で表示処理をまとめていること
  • データがない項目に備えて `不明` や `説明なし` を用意していること
  • ローディング、0件表示、エラー処理が入っていること

特に重要なのは、「API検索アプリとして必要な基本要素が一通りそろっている」という点です。

特に深掘りしたい重要ポイント

今回最も大切なのは、「APIから返るデータはきれいにそろっているとは限らない」という前提です。初心者の方は、自分で作った配列やオブジェクトに慣れていると、すべての本に著者情報や出版社情報が入っていると思いがちです。しかし実際のAPIでは、項目が欠けていることは珍しくありません。そのため、表示時には必ず「もしなかったらどうするか」を考える必要があります。これは、外部データを扱うアプリにおける非常に重要な考え方です。

また、書籍検索アプリは検索機能が中心ですが、実際にはそれだけで成立しているわけではありません。入力チェック、通信中の案内、0件時の説明、失敗時の対応、表示の見やすさ、コード整理など、多くの小さな配慮が集まって、初めて「使えるアプリ」になります。初心者の方は、総合制作を通して、機能そのものだけでなく、周辺の設計も大切だと実感できるはずです。

情報セキュリティの観点での注意

今回も、外部APIから取得したデータを画面へ表示しています。そのため、表示には textContent を使い、外部データをそのままHTMLとして解釈させない基本姿勢が大切です。また、ユーザー入力をURLへ組み込む際には encodeURIComponent() を使って安全に扱うことが重要です。さらに、エラー内容をそのまま画面に出しすぎず、ユーザー向けにはわかりやすい案内にとどめ、詳細はコンソールで確認する分け方も安全上の基本です。初心者の方は、「便利なAPIアプリほど、入力と表示の扱いを丁寧にする必要がある」と意識すると、堅実な開発感覚が育ちます。

初心者が特に注意したいポイント

Day 70では、次の点を特に意識すると理解が深まりやすいです。

  • 検索キーワードは空欄のまま送らないことが大切です。
  • ユーザー入力は `encodeURIComponent()` で安全にURLへ組み込みます。
  • APIデータは項目が欠けていることがあるため、表示の逃げ道が必要です。
  • 0件とエラーは別の状態です。
  • 取得処理、表示処理、メッセージ処理を分けると整理しやすいです。

特に重要なのは、「総合制作では、機能が動くことだけでなく、使いやすく、壊れにくく、読みやすいことまで含めて完成」と考えることです。

Day 70のゴールを整理する

Day 70では、次のことができるようになっていれば十分です。

  • 検索キーワードを使ってAPIリクエストを送れる
  • 取得した書籍データをカード形式で表示できる
  • ローディング表示を入れられる
  • 検索結果0件時に専用メッセージを出せる
  • エラー時に画面とコンソールを分けて対応できる
  • 役割ごとに関数を分けてコード整理できる

ここまでできれば、Day 70としてはとても順調です。第5フェーズで学んだ非同期処理とAPIの知識を、1つの完成したアプリとして形にできています。

まとめ

Day 70では、第5フェーズ「非同期処理とAPI」の総仕上げとして、APIを使った総合制作に取り組みました。今回は、選択肢の中から書籍検索アプリを題材に選び、入力欄から検索語を受け取り、APIへリクエストを送り、取得した書籍データをカード形式で画面へ表示する流れを学びました。その中で、入力チェック、encodeURIComponent() による安全なURL組み立て、fetch() と async / await による非同期通信、ローディング表示、0件時の案内、エラー処理、表示用関数への分割など、これまでの学習内容を総合的に活用しました。

特に大切なのは、APIアプリでは「検索できる」ことだけでなく、「入力が不十分なとき」「データが見つからないとき」「通信に失敗したとき」「一部のデータが欠けているとき」にどう振る舞うかまで考える必要があることです。そうした細かな配慮まで含めて、初めて使いやすいアプリになります。今回の総合制作を通して、非同期処理とAPI学習が、1つの実践的な作品としてつながったはずです。

次に進む前に、今回のテンプレートを実際に動かし、説明文の表示文字数を変えたり、著者ではなく出版日も表示したり、Enterキーでも検索できるようにしたりするのがおすすめです。そして、「どの処理が入力担当で、どの処理が通信担当で、どの処理が表示担当なのか」を自分の言葉で説明できるようになると、Day 70の理解がかなり深まっています。

タイトルとURLをコピーしました