2日目のゴールと全体像
2日目のテーマは
「1日目で作った“動くニュース検索”を、実用レベルに近づけること」です。
具体的には、こういうところを狙います。
単なる「キーワード検索」から一歩進めて、期間や並び順などの条件を増やすこと。
fetch と async/await の書き方はそのままに、コードを読みやすく整理すること。
エラーハンドリングを“ユーザー目線”で少し丁寧にすること。
ローディング表示を「状態」として扱う感覚をつかむこと。
1日目で作ったものを「書き直す」のではなく、「育てる」イメージで進めます。
1日目のコードを「型」として再確認する
中心となる非同期関数の形
まず、1日目の fetchNews は、すでにかなり良い形をしています。
async function fetchNews(keyword) {
statusDiv.textContent = "ニュースを取得中です…";
resultDiv.textContent = "";
searchButton.disabled = true;
try {
const url = buildUrl(keyword);
const response = await fetch(url);
if (!response.ok) {
statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
return;
}
const data = await response.json();
if (data.status === "error") {
statusDiv.textContent = `エラー:${data.message}`;
return;
}
statusDiv.textContent = "ニュースの取得に成功しました。";
renderArticles(data);
} catch (error) {
statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
console.error(error);
} finally {
searchButton.disabled = false;
}
}
JavaScriptここで、すでに大事なポイントは押さえられています。
非同期処理を async 関数に閉じ込めていること。
await で fetch と json 変換を順番に待っていること。
try の中で「成功時の処理」、catch で「通信エラー」、finally で「後片付け」をしていること。
HTTP レベルのエラー(response.ok)と、API 独自のエラー(data.status === “error”)を分けていること。
2日目は、この「型」を崩さずに、周りを強化していきます。
入力バリデーションを少しだけ“本気”にする
キーワードのチェックを関数に切り出す
1日目では「空文字かどうか」だけをチェックしていました。
2日目では、バリデーションを関数にして、少しだけルールを増やします。
function validateKeyword(keyword) {
if (!keyword.trim()) {
return "キーワードを入力してください。";
}
if (keyword.length > 50) {
return "キーワードが長すぎます。50文字以内で入力してください。";
}
return null;
}
JavaScriptこの関数は「問題があればエラーメッセージを返す」「問題なければ null を返す」という形にしておくと、とても扱いやすくなります。
fetchNews の入り口でバリデーションを使う
async function fetchNews(keyword) {
const error = validateKeyword(keyword);
if (error) {
statusDiv.textContent = error;
resultDiv.textContent = "";
return;
}
statusDiv.textContent = "ニュースを取得中です…";
resultDiv.textContent = "";
searchButton.disabled = true;
try {
const url = buildUrl(keyword);
const response = await fetch(url);
// 以下は 1日目と同じ流れ
} catch (error) {
// 省略
} finally {
searchButton.disabled = false;
}
}
JavaScriptここでのポイントは、「バリデーションで引っかかったら、そもそも fetch を呼ばない」ということです。
これは API に無駄なリクエストを送らない、という意味でも大事ですし、ユーザーにとっても「何が悪いのか」が明確になります。
NewsAPI のパラメータを増やして“検索条件”を体験する
日付範囲や並び順を UI に追加する
NewsAPI の /everything には、例えばこんなパラメータがあります。
from:いつからのニュースか(例: 2026-01-20)to:いつまでのニュースかsortBy:並び順(publishedAt, relevancy, popularity など)
2日目では、次のような UI を足してみましょう。
<input id="keywordInput" placeholder="キーワード (例: JavaScript)" />
<label>
開始日:
<input id="fromInput" type="date" />
</label>
<label>
終了日:
<input id="toInput" type="date" />
</label>
<select id="sortSelect">
<option value="publishedAt">新しい順</option>
<option value="relevancy">関連度順</option>
<option value="popularity">人気順</option>
</select>
<button id="searchButton">ニュース検索</button>
<div id="status"></div>
<div id="result"></div>
JavaScript 側で要素を取得します。
const keywordInput = document.getElementById("keywordInput");
const fromInput = document.getElementById("fromInput");
const toInput = document.getElementById("toInput");
const sortSelect = document.getElementById("sortSelect");
JavaScriptbuildUrl を「オプション付き」にする
1日目の buildUrl はキーワードだけを受け取っていました。
2日目では、オブジェクトを受け取る形にして、柔軟にします。
const API_KEY = "YOUR_API_KEY";
const baseUrl = "https://newsapi.org/v2/everything";
function buildUrl({ keyword, from, to, sortBy }) {
const params = new URLSearchParams({
q: keyword,
apiKey: API_KEY,
language: "ja",
pageSize: "10",
sortBy: sortBy || "publishedAt"
});
if (from) {
params.set("from", from);
}
if (to) {
params.set("to", to);
}
return `${baseUrl}?${params.toString()}`;
}
JavaScriptここで大事なのは、「必須のもの(q, apiKey)は必ず入れる」「任意のもの(from, to)はあれば入れる」という考え方です。
fetchNews から buildUrl を呼ぶときの形
async function fetchNews() {
const keyword = keywordInput.value.trim();
const from = fromInput.value;
const to = toInput.value;
const sortBy = sortSelect.value;
const error = validateKeyword(keyword);
if (error) {
statusDiv.textContent = error;
resultDiv.textContent = "";
return;
}
statusDiv.textContent = "ニュースを取得中です…";
resultDiv.textContent = "";
searchButton.disabled = true;
try {
const url = buildUrl({ keyword, from, to, sortBy });
const response = await fetch(url);
if (!response.ok) {
statusDiv.textContent = `サーバーエラーが発生しました。(${response.status})`;
return;
}
const data = await response.json();
if (data.status === "error") {
statusDiv.textContent = `エラー:${data.message}`;
return;
}
statusDiv.textContent = "ニュースの取得に成功しました。";
renderArticles(data);
} catch (error) {
statusDiv.textContent = "通信に失敗しました。ネットワークを確認してください。";
console.error(error);
} finally {
searchButton.disabled = false;
}
}
JavaScriptここで、1日目との違いは「URL を作るときに、複数の条件を渡している」だけです。
fetch の書き方、async/await、try-catch-finally の構造はまったく同じです。
ローディング表示を「状態」として扱う感覚をつかむ
isLoading というフラグを導入する
2日目では、ローディングを少しだけ“ちゃんと状態として扱う”ことを意識してみましょう。
let isLoading = false;
function startLoading() {
isLoading = true;
statusDiv.textContent = "ニュースを取得中です…";
resultDiv.textContent = "";
searchButton.disabled = true;
}
function endLoading() {
isLoading = false;
searchButton.disabled = false;
}
JavaScriptそして、fetchNews の中ではこう使います。
async function fetchNews() {
const keyword = keywordInput.value.trim();
const from = fromInput.value;
const to = toInput.value;
const sortBy = sortSelect.value;
const error = validateKeyword(keyword);
if (error) {
statusDiv.textContent = error;
resultDiv.textContent = "";
return;
}
startLoading();
try {
const url = buildUrl({ keyword, from, to, sortBy });
const response = await fetch(url);
// 以下同じ
} catch (error) {
// 同じ
} finally {
endLoading();
}
}
JavaScript「ローディング中かどうか」を変数で持っておくと、
例えば「ローディング中は Enter キーでの再検索を無効にする」など、
後から拡張しやすくなります。
エラーハンドリングを“ユーザー目線”で一段丁寧にする
エラー表示用の小さな関数を作る
1日目では、エラーが起きるたびに statusDiv.textContent = ... と書いていました。
2日目では、少しだけ整理して「意味のある名前の関数」にまとめます。
function showError(message) {
statusDiv.textContent = message;
statusDiv.style.color = "red";
}
function showStatus(message) {
statusDiv.textContent = message;
statusDiv.style.color = "black";
}
JavaScriptこれを使うと、fetchNews はこう書けます。
async function fetchNews() {
const keyword = keywordInput.value.trim();
const from = fromInput.value;
const to = toInput.value;
const sortBy = sortSelect.value;
const error = validateKeyword(keyword);
if (error) {
showError(error);
resultDiv.textContent = "";
return;
}
startLoading();
showStatus("ニュースを取得中です…");
try {
const url = buildUrl({ keyword, from, to, sortBy });
const response = await fetch(url);
if (!response.ok) {
showError(`サーバーエラーが発生しました。(${response.status})`);
return;
}
const data = await response.json();
if (data.status === "error") {
showError(`エラー:${data.message}`);
return;
}
showStatus("ニュースの取得に成功しました。");
renderArticles(data);
} catch (error) {
showError("通信に失敗しました。ネットワークを確認してください。");
console.error(error);
} finally {
endLoading();
}
}
JavaScriptここでのポイントは、「成功メッセージ」と「エラーメッセージ」を視覚的にも区別していることです。
ユーザーにとって「今どういう状態なのか」が、文字だけでなく色でも伝わるようになります。
2日目の完成コード(重要部分をまとめて)
const API_KEY = "YOUR_API_KEY";
const baseUrl = "https://newsapi.org/v2/everything";
const keywordInput = document.getElementById("keywordInput");
const fromInput = document.getElementById("fromInput");
const toInput = document.getElementById("toInput");
const sortSelect = document.getElementById("sortSelect");
const searchButton = document.getElementById("searchButton");
const statusDiv = document.getElementById("status");
const resultDiv = document.getElementById("result");
let isLoading = false;
function validateKeyword(keyword) {
if (!keyword.trim()) {
return "キーワードを入力してください。";
}
if (keyword.length > 50) {
return "キーワードが長すぎます。50文字以内で入力してください。";
}
return null;
}
function buildUrl({ keyword, from, to, sortBy }) {
const params = new URLSearchParams({
q: keyword,
apiKey: API_KEY,
language: "ja",
pageSize: "10",
sortBy: sortBy || "publishedAt"
});
if (from) {
params.set("from", from);
}
if (to) {
params.set("to", to);
}
return `${baseUrl}?${params.toString()}`;
}
function showError(message) {
statusDiv.textContent = message;
statusDiv.style.color = "red";
}
function showStatus(message) {
statusDiv.textContent = message;
statusDiv.style.color = "black";
}
function startLoading() {
isLoading = true;
searchButton.disabled = true;
}
function endLoading() {
isLoading = false;
searchButton.disabled = false;
}
function renderArticles(data) {
const articles = data.articles;
if (!articles || articles.length === 0) {
resultDiv.textContent = "該当するニュースが見つかりませんでした。";
return;
}
let html = "";
articles.forEach((article) => {
const title = article.title || "タイトルなし";
const description = article.description || "説明文はありません。";
const url = article.url;
const source = article.source?.name || "不明なソース";
html += `
<div class="article">
<h3>${title}</h3>
<p>${description}</p>
<p>提供元:${source}</p>
<p><a href="${url}" target="_blank" rel="noopener noreferrer">記事を読む</a></p>
</div>
`;
});
resultDiv.innerHTML = html;
}
async function fetchNews() {
if (isLoading) {
return;
}
const keyword = keywordInput.value.trim();
const from = fromInput.value;
const to = toInput.value;
const sortBy = sortSelect.value;
const error = validateKeyword(keyword);
if (error) {
showError(error);
resultDiv.textContent = "";
return;
}
startLoading();
showStatus("ニュースを取得中です…");
resultDiv.textContent = "";
try {
const url = buildUrl({ keyword, from, to, sortBy });
const response = await fetch(url);
if (!response.ok) {
showError(`サーバーエラーが発生しました。(${response.status})`);
return;
}
const data = await response.json();
if (data.status === "error") {
showError(`エラー:${data.message}`);
return;
}
showStatus("ニュースの取得に成功しました。");
renderArticles(data);
} catch (error) {
showError("通信に失敗しました。ネットワークを確認してください。");
console.error(error);
} finally {
endLoading();
}
}
searchButton.addEventListener("click", () => {
fetchNews();
});
JavaScript今日いちばん深く理解してほしいこと
2日目で本当に持ち帰ってほしいのは、次の感覚です。
fetch と async/await の「型」はもう変えなくていい。
変わるのは「URL の組み立て方」と「レスポンスの使い方」だけだということ。
ローディングやエラー表示は、“状態”として扱うと設計が楽になること。
バリデーションやエラー表示を関数に切り出すと、コードが一気に読みやすくなること。
天気 API でも、ニュース API でも、
やっていることの本質は同じです。
3日目以降は、このニュースアプリに
ページネーション(次のページのニュース)や
検索履歴、ソース別フィルタなどを足して、
「毎日使えるニュースビューア」に近づけていきましょう。

