JavaScript | 1 日 120 分 × 7 日アプリ学習:API通信アプリ(ExchangeRate.host)

APP JavaScript
スポンサーリンク

2日目のゴールと今日やること

2日目のテーマは
「1日目の“レートを見るだけアプリ”を、“ちゃんと使える通貨変換ツール”に育てる」 ことです。

やることはこうです。

  • 金額を入力して「いくらになるか」まで計算する
  • 入力チェックをちゃんとやる(未入力・マイナス・文字など)
  • ローディング表示を少し丁寧にする
  • エラーメッセージを「原因別」に分ける

技術の柱は昨日と同じです。

  • fetch
  • Promise / async‑await
  • エラーハンドリング(try / catch)

今日は「新しい文法」よりも、
昨日の型を“現実のアプリ”に寄せていく作業 です。


1日目のアプリをざっくり振り返る

昨日の流れを言葉で確認する

1日目のアプリは、こんな流れでした。

  • from 通貨(例:JPY)を選ぶ
  • to 通貨(例:USD)を選ぶ
  • 「レート取得」ボタンを押す
  • fetch で ExchangeRate.host の /convert にアクセス
  • JSON をパース
  • success や result をチェック
  • レートを表示
  • 通信失敗時にメッセージを出す

つまり、「1 JPY = 0.00… USD」を見るところまではできている状態です。

2日目では、ここに 「金額 × レート」 を足していきます。


金額入力を追加する

HTML のイメージ

頭の中で、こんな UI をイメージしてください。

<input id="amountInput" type="number" placeholder="金額を入力(例: 1000)" />

<select id="fromSelect">
  <option value="JPY">JPY</option>
  <option value="USD">USD</option>
  <option value="EUR">EUR</option>
</select>

<select id="toSelect">
  <option value="USD">USD</option>
  <option value="JPY">JPY</option>
  <option value="EUR">EUR</option>
</select>

<button id="convertButton">通貨変換</button>

<div id="status"></div>
<div id="result"></div>

JavaScript 側では、こう取ります。

const amountInput = document.getElementById("amountInput");
const fromSelect = document.getElementById("fromSelect");
const toSelect = document.getElementById("toSelect");
const convertButton = document.getElementById("convertButton");
const statusDiv = document.getElementById("status");
const resultDiv = document.getElementById("result");
JavaScript

金額の入力チェックをちゃんとやる

「空じゃないか」だけでは足りない

金額入力で起こりがちなパターンはこうです。

  • 空欄
  • 0
  • マイナス
  • 文字列(ブラウザによっては入り得る)
  • めちゃくちゃ大きい数

これを一箇所でチェックする関数を作ります。

function parseAmount(raw) {
  if (!raw) {
    return { ok: false, message: "金額を入力してください。" };
  }

  const amount = Number(raw);

  if (!Number.isFinite(amount)) {
    return { ok: false, message: "金額は数値で入力してください。" };
  }

  if (amount <= 0) {
    return { ok: false, message: "金額は 1 以上を入力してください。" };
  }

  if (amount > 1_000_000_000) {
    return { ok: false, message: "金額が大きすぎます。もう少し小さい値を入力してください。" };
  }

  return { ok: true, value: amount };
}
JavaScript

深掘りポイント

ここでやっているのは、

  • 「入力されているか」
  • 「数値として解釈できるか」
  • 「0 より大きいか」
  • 「現実的な範囲か」

を分けてチェックしていることです。

エラーメッセージも「何がダメなのか」がわかるようにしてあります。


ExchangeRate.host の amount パラメータを使うかどうか

2つのやり方がある

通貨変換には、実は 2 パターンあります。

  1. 「1 単位のレート」を取って、自分で掛け算する
  2. API に amount を渡して「いくらになるか」まで計算してもらう

ExchangeRate.host の /convert は、amount を渡せます。

https://api.exchangerate.host/convert?from=JPY&to=USD&amount=1000

返ってくる JSON の result が、
「1000 JPY が何 USD か」になります。

今日は 2番の「API に計算してもらう」 パターンでいきます。
その方がコードがシンプルで、API の使い方もよくわかります。


ローディング表示を少し丁寧にする

1日目のパターンを思い出す

昨日はこんな感じでした。

function startLoading(message) {
  statusDiv.textContent = message || "取得中です…";
  convertButton.disabled = true;
}

function endLoading() {
  convertButton.disabled = false;
}
JavaScript

2日目でもこの形を使いますが、
メッセージを「何をしているか」に合わせて変えます。


通貨変換関数の全体像(2日目版)

ここが今日のメインです。
金額入力・fetch・async/await・エラーハンドリング・ローディング
が全部入った関数を書きます。

async function convertCurrency() {
  const rawAmount = amountInput.value.trim();
  const from = fromSelect.value;
  const to = toSelect.value;

  if (from === to) {
    statusDiv.textContent = "異なる通貨を選択してください。";
    resultDiv.textContent = "";
    return;
  }

  const parsed = parseAmount(rawAmount);
  if (!parsed.ok) {
    statusDiv.textContent = parsed.message;
    resultDiv.textContent = "";
    return;
  }

  const amount = parsed.value;

  startLoading(`${amount} ${from}${to} に変換中です…`);
  resultDiv.textContent = "";

  try {
    const url =
      `https://api.exchangerate.host/convert` +
      `?from=${encodeURIComponent(from)}` +
      `&to=${encodeURIComponent(to)}` +
      `&amount=${encodeURIComponent(amount)}`;

    const response = await fetch(url);

    if (!response.ok) {
      if (response.status >= 500) {
        statusDiv.textContent = "サーバー側でエラーが発生しています。時間をおいて再試行してください。";
      } else {
        statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
      }
      return;
    }

    const data = await response.json();

    if (!data || data.success === false) {
      statusDiv.textContent = "レートの取得に失敗しました。";
      console.error("API error response:", data);
      return;
    }

    if (typeof data.result !== "number") {
      statusDiv.textContent = "予期しない形式のデータが返されました。";
      console.error("Unexpected data:", data);
      return;
    }

    statusDiv.textContent = "通貨変換に成功しました。";
    renderConversion(from, to, amount, data.result, data.info?.rate);

  } catch (error) {
    statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
    console.error(error);

  } finally {
    endLoading();
  }
}
JavaScript

ここを、重要ポイントごとに分解していきます。


重要ポイント1:入力チェックを「通貨」と「金額」で分ける

if (from === to) {
  statusDiv.textContent = "異なる通貨を選択してください。";
  resultDiv.textContent = "";
  return;
}

const parsed = parseAmount(rawAmount);
if (!parsed.ok) {
  statusDiv.textContent = parsed.message;
  resultDiv.textContent = "";
  return;
}
JavaScript

ここで、

  • 通貨の組み合わせが妥当か
  • 金額が妥当か

を別々にチェックしています。

これにより、
「どこが悪いのか」がユーザーに伝わりやすくなります。


重要ポイント2:URL を“安全に”組み立てる

const url =
  `https://api.exchangerate.host/convert` +
  `?from=${encodeURIComponent(from)}` +
  `&to=${encodeURIComponent(to)}` +
  `&amount=${encodeURIComponent(amount)}`;
JavaScript

encodeURIComponent を使っているのは、
通貨コードや金額に予期せぬ文字が入っても
URL として壊れないようにするためです。

今回は通貨コードが英大文字、金額が数字なので、
実際には問題になりにくいですが、
「パラメータは encode する」 という癖をつけておくと、
他の API を扱うときに役立ちます。


重要ポイント3:HTTP エラーを「サーバー側」と「その他」で分ける

if (!response.ok) {
  if (response.status >= 500) {
    statusDiv.textContent = "サーバー側でエラーが発生しています。時間をおいて再試行してください。";
  } else {
    statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
  }
  return;
}
JavaScript

ここでやっているのは、

  • 500 番台 → サーバー側の問題
  • それ以外 → 一般的なサーバーエラー

という分け方です。

ユーザーにとっては、

「自分の入力が悪いのか」
「サーバーが悪いのか」

がわかるだけで、ストレスがかなり減ります。


重要ポイント4:API レベルの失敗とデータ形式のチェック

if (!data || data.success === false) {
  statusDiv.textContent = "レートの取得に失敗しました。";
  console.error("API error response:", data);
  return;
}

if (typeof data.result !== "number") {
  statusDiv.textContent = "予期しない形式のデータが返されました。";
  console.error("Unexpected data:", data);
  return;
}
JavaScript

ここで、

  • HTTP 的には成功でも、API 的には失敗 → 「レートの取得に失敗しました。」
  • result が数値でない → 「予期しない形式のデータ」

と分けています。

「通信は成功したけど、データがおかしい」
というケースをちゃんと扱っているのがポイントです。


重要ポイント5:catch で「通信失敗」を一括で扱う

} catch (error) {
  statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
  console.error(error);
}
JavaScript

ここは 1日目と同じですが、
改めてとても重要です。

  • Wi‑Fi が切れている
  • DNS が引けない
  • ブラウザがオフライン

など、HTTP ステータス以前の問題は、
ここで一括して「通信失敗」として扱います。


通貨変換結果を表示する

レートと変換結果を両方見せる

function renderConversion(from, to, amount, converted, rate) {
  const rateText =
    typeof rate === "number"
      ? `1 ${from} = ${rate} ${to}`
      : "";

  const html = `
    <h3>通貨変換結果</h3>
    <p>${amount} ${from} = ${converted} ${to}</p>
    <p>${rateText}</p>
  `;
  resultDiv.innerHTML = html;
}
JavaScript

ここで、

  • 「1000 JPY = 6.8 USD」
  • 「1 JPY = 0.0068 USD」

のように、
変換結果とレートの両方 を表示しています。

ユーザーにとっては、
「このレートで計算されたんだな」が見えると安心感が増します。


ボタンにイベントをつなぐ

convertButton.addEventListener("click", convertCurrency);
JavaScript

これで、

  • 金額を入力
  • 通貨を選ぶ
  • ボタンを押す
  • ローディング開始
  • API から取得
  • 成功 or 失敗で分岐
  • ローディング終了

という一連の流れが完成します。


2日目のまとめ

今日あなたがやったことを、言葉で整理してみます。

  • 金額入力欄を追加した
  • parseAmount で「空・数値・0 以下・大きすぎる」をチェックした
  • 通貨の組み合わせ(from と to が同じ)を最初に弾いた
  • ExchangeRate.host の /convert に amount を渡して「変換後の金額」まで取得した
  • URL を encodeURIComponent で安全に組み立てた
  • HTTP エラーを「サーバー側(500 番台)」と「その他」で分けてメッセージを変えた
  • API レベルの success と result の型をチェックした
  • ローディング表示を「何をしているか」に合わせて出した
  • 通信失敗時に「ネットワークを確認してください」と伝えた
  • 変換結果とレートの両方を表示する renderConversion を作った

どれも新しい文法ではなく、
「昨日の型を、現実の通貨変換アプリに寄せていった」 内容です。


今日いちばん深く理解してほしいこと

2日目の本質は、

「fetch / async‑await / エラーハンドリングは、“入力チェック”と組み合わさったときにアプリになる」

ということです。

通貨の組み合わせ
金額の妥当性
サーバーの状態
API の成功・失敗
データ形式の妥当性
ネットワークの状態

これらをひとつずつ分けて考え、
それぞれに合ったメッセージを出してあげる。

それができているあなたは、
もう「API を叩いてみる人」ではなく、
「API を使ったツールを設計する人」 の側に立っています。

3日目では、この通貨変換アプリに

  • 「逆方向に変換」
  • 「よく使う通貨ペアのプリセット」
  • 「入力の自動補完」

などを足して、
さらに“使っていて気持ちいいツール”に育てていきます。

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