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

APP JavaScript
スポンサーリンク

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

3日目のテーマは
「1日分の天気 → 複数日(予報)に広げても、同じ fetch・async/await・エラーハンドリングで考えられるようになること」
です。

1日目・2日目で、あなたはすでに

fetch で API を叩く
async/await で読みやすく書く
try-catch と response.ok、data.error でエラーを分岐する
ローディング表示とボタン制御を入れる

という「API 通信の基本フォーム」を身につけました。

3日目はこれをそのまま使いながら、

現在の天気 API(current)ではなく
予報 API(forecast)を叩いて
「複数日の天気」を表示する

という、少しだけ“スケールアップしたアプリ”にしていきます。


今日の完成イメージを先に描く

どんなアプリにするか

今日のミニアプリは、こんな動きをします。

都市名を入力する
「検索」ボタンを押す
WeatherAPI.com の forecast API を叩く
今日を含む 3 日分の天気を取得する
ローディング中は「取得中…」と表示
成功したら「日付・最高/最低気温・天気」を一覧表示
失敗したら「エラー内容」をわかりやすく表示

つまり、
「1件のデータを表示」から
「配列(複数件)をループして表示」
にステップアップします。

ここで大事なのは、
ロジックの“骨格”は 1 日目・2 日目とまったく同じ、
という感覚を持つことです。


forecast API に変えるだけで「複数日」が取れる

current API と forecast API の違い

1日目・2日目で使っていたのは、
だいたいこんな URL でした。

https://api.weatherapi.com/v1/current.json?key=API_KEY&q=Tokyo&lang=ja

3日目で使うのは、
forecast.json です。

https://api.weatherapi.com/v1/forecast.json?key=API_KEY&q=Tokyo&days=3&lang=ja

違いは 2 つだけです。

パスが current.json → forecast.json
クエリに days=3 が増えた(何日分ほしいか)

JavaScript で書くとこうなります。

const API_KEY = "YOUR_API_KEY";
const baseUrl = "https://api.weatherapi.com/v1/forecast.json";

function buildUrl(city, days = 3) {
  const params = new URLSearchParams({
    key: API_KEY,
    q: city,
    days: String(days),
    lang: "ja"
  });
  return `${baseUrl}?${params.toString()}`;
}
JavaScript

ここまでで、
「複数日を取る準備」は完了です。


fetch と async/await は「昨日と同じフォーム」で使う

骨格は変えない

2日目の fetchWeather を、
ほぼそのまま forecast 用に書き換えます。

async function fetchForecast(city) {
  const error = validateCity(city);
  if (error) {
    statusDiv.textContent = error;
    resultDiv.textContent = "";
    return;
  }

  startLoading();

  try {
    const url = buildUrl(city, 3);
    const response = await fetch(url);

    if (!response.ok) {
      statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
      return;
    }

    const data = await response.json();

    if (data.error) {
      statusDiv.textContent = `エラー:${data.error.message}`;
      return;
    }

    statusDiv.textContent = "3日分の天気を取得しました。";
    renderForecast(data);

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

  } finally {
    endLoading();
  }
}
JavaScript

ここで意識してほしいのは、

fetch
await
response.ok チェック
data.error チェック
try-catch-finally

という“型”は、
昨日とまったく同じだということです。

中級に上がるって、
「新しいことを覚える」より
「同じ型をいろんな場面に当てはめられる」
ことなんですよね。


forecast のレスポンス構造を読み解く

どんな JSON が返ってくるか

forecast.json は、ざっくりこんな構造です。

{
  "location": {
    "name": "Tokyo",
    "country": "Japan"
  },
  "current": {
    "temp_c": 26.5,
    "condition": { "text": "晴れ" }
  },
  "forecast": {
    "forecastday": [
      {
        "date": "2026-01-27",
        "day": {
          "maxtemp_c": 28.0,
          "mintemp_c": 20.0,
          "condition": { "text": "晴れ" }
        }
      },
      {
        "date": "2026-01-28",
        "day": {
          "maxtemp_c": 27.0,
          "mintemp_c": 19.0,
          "condition": { "text": "にわか雨" }
        }
      }
    ]
  }
}

複数日分は
forecast.forecastday という配列に入っています。

つまり、

location.name / country → 都市情報
forecast.forecastday → 日ごとの天気一覧

というイメージで見れば OK です。


複数日分をループして表示する

renderForecast の役割

renderForecast は、
「1 日分」ではなく
「配列を全部表示する」 関数になります。

function renderForecast(data) {
  const name = data.location.name;
  const country = data.location.country;
  const days = data.forecast.forecastday;

  let html = "";
  html += `<p>${name} (${country}) の 3 日間の天気</p>`;

  days.forEach((day) => {
    const date = day.date;
    const max = day.day.maxtemp_c;
    const min = day.day.mintemp_c;
    const text = day.day.condition.text;

    html += `
      <div class="day">
        <p>日付:${date}</p>
        <p>最高:${max} ℃ / 最低:${min} ℃</p>
        <p>天気:${text}</p>
      </div>
    `;
  });

  resultDiv.innerHTML = html;
}
JavaScript

ここでのポイントは、

配列 days を forEach で回して
1 日分ずつ HTML を足していく

というシンプルな構造です。

深掘り:API 通信 × 配列処理ができると一気に世界が広がる

今やっていることは、

API から配列を受け取る
配列をループして UI を作る

という、
「ほぼすべての Web アプリで使うパターン」です。

天気だけじゃなくて、

商品一覧
ユーザー一覧
チャット履歴
ニュース記事

など、
「一覧表示するもの」は全部この形になります。


エラーハンドリングを「複数日でも同じように」考える

失敗パターンは昨日と変わらない

複数日を取ろうが、
1 日だけ取ろうが、
失敗パターンは同じです。

ネットワークエラー(catch)
HTTP エラー(!response.ok)
API 独自のエラー(data.error)

大事なのは、
「どの段階で失敗したか」を意識してメッセージを変える
ことです。

例えば、

response.ok が false → サーバー側の問題の可能性
data.error がある → 入力や API キーの問題の可能性

なので、メッセージも変えられます。

if (!response.ok) {
  statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
  return;
}

const data = await response.json();

if (data.error) {
  statusDiv.textContent = `エラー:${data.error.message}`;
  return;
}
JavaScript

ここまで分けてあげると、
ユーザーも「自分のせいか、サーバーのせいか」が
なんとなくわかります。


ローディング表示は「複数日でもまったく同じ」

startLoading / endLoading をそのまま使う

2日目で作ったローディング関数は、
そのまま再利用できます。

function startLoading() {
  statusDiv.textContent = "天気情報を取得中です…";
  resultDiv.textContent = "";
  searchButton.disabled = true;
}

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

fetchForecast の中で
try の前に startLoading
finally の中で endLoading

という“型”を守るだけです。

深掘り:ローディングは「処理の始まりと終わりを挟む」

ローディング表示は、
「非同期処理の前後を挟む」
というイメージで考えると、
どんな API 通信にも応用できます。


3日目の全体コード(重要部分をまとめて)

const API_KEY = "YOUR_API_KEY";
const baseUrl = "https://api.weatherapi.com/v1/forecast.json";

const cityInput = document.getElementById("cityInput");
const searchButton = document.getElementById("searchButton");
const statusDiv = document.getElementById("status");
const resultDiv = document.getElementById("result");

function buildUrl(city, days = 3) {
  const params = new URLSearchParams({
    key: API_KEY,
    q: city,
    days: String(days),
    lang: "ja"
  });
  return `${baseUrl}?${params.toString()}`;
}

function validateCity(city) {
  if (!city.trim()) return "都市名を入力してください。";
  if (city.length > 50) return "都市名が長すぎます。50文字以内で入力してください。";
  return null;
}

function startLoading() {
  statusDiv.textContent = "天気情報を取得中です…";
  resultDiv.textContent = "";
  searchButton.disabled = true;
}

function endLoading() {
  searchButton.disabled = false;
}

function renderForecast(data) {
  const name = data.location.name;
  const country = data.location.country;
  const days = data.forecast.forecastday;

  let html = "";
  html += `<p>${name} (${country}) の 3 日間の天気</p>`;

  days.forEach((day) => {
    const date = day.date;
    const max = day.day.maxtemp_c;
    const min = day.day.mintemp_c;
    const text = day.day.condition.text;

    html += `
      <div class="day">
        <p>日付:${date}</p>
        <p>最高:${max} ℃ / 最低:${min} ℃</p>
        <p>天気:${text}</p>
      </div>
    `;
  });

  resultDiv.innerHTML = html;
}

async function fetchForecast(city) {
  const error = validateCity(city);
  if (error) {
    statusDiv.textContent = error;
    resultDiv.textContent = "";
    return;
  }

  startLoading();

  try {
    const url = buildUrl(city, 3);
    const response = await fetch(url);

    if (!response.ok) {
      statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
      return;
    }

    const data = await response.json();

    if (data.error) {
      statusDiv.textContent = `エラー:${data.error.message}`;
      return;
    }

    statusDiv.textContent = "3日分の天気を取得しました。";
    renderForecast(data);

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

  } finally {
    endLoading();
  }
}

searchButton.addEventListener("click", () => {
  fetchForecast(cityInput.value);
});
JavaScript

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

3日目で本当に持ち帰ってほしいのは、

API が「1件」でも「複数件」でも
fetch・async/await・エラーハンドリングの“型”は変わらない

という感覚です。

違うのは、

レスポンスの中身(current か forecast か)
配列をループするかどうか

だけです。

つまり、あなたはもう

「API の仕様書を読んで、
それに合わせて URL を組み立てて、
返ってきた JSON を自分で解釈して UI に落とし込める人」

になりつつあります。

次の 4 日目では、
この forecast アプリに

天気アイコンの表示
摂氏 / 華氏の切り替え
エラー時の UI 改善

などを足して、
さらに“プロダクト寄り”の仕上がりにしていきます。

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