前回はmockAPIを使ってREST APIを作成しました。今回はそのAPIを使ってHTML+CSS+JavaScriptで操作するCRUDアプリを作成していきます。
「素の JavaScript で REST API を扱う感覚」がわかる、学習に最適な構成です。
確認
前回作成したAPIは
https://69368643f8dc350aff312a6c.mockapi.io/quotes
にアクセスすると以下のような
– id
– 名言(日本語)
– 名言(英語)
-意味
を返却するAPIです。

今回はこのAPIに接続し、データの作成、一覧、更新、削除ができるいわゆるCRUDアプリを作成していきましょう。
作成
ファイル構成
好きな場所に新規にフォルダを作成し(RomandsDoApp)その中にcssフォルダ、JSフォルダ、index.htmlを作成します。

index.html
まずは以下のようにindex.htmlを作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>RomansDo JS Client</title>
<link rel="stylesheet" href="css/main.css">
<script type="module" src="js/main.js"></script>
</head>
<body>
<div class="container">
<h1>RomansDo Quotes Client</h1>
<p class="status" id="status">読み込み中...</p>
<!-- 入力フォーム -->
<form id="quoteForm">
<input type="hidden" id="quoteId" />
<div>
<label for="jp">日本語のことわざ (jp)</label><br />
<input type="text" id="jp" required />
</div>
<div>
<label for="en">英語 (en)</label><br />
<input type="text" id="en" required />
</div>
<label for="description">説明 (description)</label>
<textarea id="description"></textarea>
<div class="buttons">
<button type="button" class="btn-secondary" id="resetBtn">キャンセル</button>
<button type="submit" class="btn-primary" id="submitBtn">新規作成</button>
</div>
</form>
<!-- 一覧テーブル -->
<table>
<thead>
<tr>
<th>ID</th>
<th>日本語 (jp)</th>
<th>英語 (en)</th>
<th>説明</th>
<th>操作</th>
</tr>
</thead>
<tbody id="quotesBody">
<!-- JS で埋め込み -->
</tbody>
</table>
</div><!--container-->
</body>
</html> ポイント解説:type="module" を使った JavaScript 読み込み
<script type="module" src="js/main.js"></script>この一行が、今回の HTML における 最重要ポイントです。
なぜ type="module" を使っているのか?
type="module" を指定すると、JavaScript ファイルは
ES Modules(モジュール) として読み込まれます。
これにより、従来の <script> にはなかった便利な挙動が
いくつも自動的に有効になります。
① 自動的に strict mode になる
type="module" を指定した JavaScript は、
自動的に strict mode で実行されます。
// 'use strict'; を書かなくてよい- 変数の暗黙的なグローバル化を防止
- 曖昧な挙動や事故りやすい書き方を最初から排除できる
👉 安全な JavaScript がデフォルトになる
② 自動的に defer と同じ挙動になる
モジュールスクリプトは、
HTML の解析が完了してから実行されます。
つまり、次の指定だけで、
<script type="module" src="js/main.js"></script>以下と同じ効果があります。
<script src="js/main.js" defer></script>- DOM 読み込み前に JS が走ってエラーになる心配がない
DOMContentLoadedを待つコードが不要になる
👉 DOM 操作をトップレベルに安心して書ける
③ グローバル汚染を防げる
type="module" で読み込まれた JavaScript は、
ファイル単位で独立したスコープを持ちます。
// main.js
const foo = 123;この foo は window.foo にはなりません。
- 変数名の衝突を防げる
- 即時関数で囲む必要がなくなる
👉 昔よく使われていたこの書き方が不要になる
(function () {
// スコープ確保のための即時関数
})();まとめ
type="module" を使うことで、
- strict mode
- defer 相当の遅延実行
- グローバル汚染防止
が すべてデフォルトで有効になります。
フレームワークを使わなくても、
安全でモダンな JavaScript 開発ができるのが大きなメリットです。
css/main.css
cssフォルダの中にmain.cssファイルを作成し以下のように記述
body {
margin: 0;
padding: 24px;
background: #f5f5f7;
}
h1 {
margin-top: 0;
}
.container {
max-width: 960px;
margin: 0 auto;
background: #fff;
padding: 24px;
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0,0,0,0.06);
}
form {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 16px;
margin-bottom: 24px;
align-items: center;
}
form label {
font-size: 0.9rem;
color: #555;
}
form input, form textarea {
width: 100%;
padding: 6px 8px;
border-radius: 6px;
border: 1px solid #ccc;
font-size: 0.95rem;
box-sizing: border-box;
}
form textarea {
grid-column: 1 / 3;
min-height: 60px;
/*縦方向のリサイズだけ許可*/
resize: vertical;
}
.buttons {
grid-column: 1 / 3;
display: flex;
gap: 8px;
justify-content: flex-end;
}
button {
border: none;
border-radius: 999px;
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
}
.btn-primary {
background: #2563eb;
color: #fff;
}
.btn-secondary {
background: #e5e7eb;
color: #111827;
}
.btn-danger {
background: #ef4444;
color: #fff;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
th, td {
border-bottom: 1px solid #e5e7eb;
padding: 8px 6px;
vertical-align: top;
}
th {
text-align: left;
background: #f9fafb;
}
.actions button {
margin-right: 4px;
}
.status {
margin-bottom: 12px;
font-size: 0.85rem;
color: #555;
}
display:grid
- 今回formの要素をgridレイアウトで並べている。gridの知識を深めたい場合は以下のアプリがおすすめ

js/main.js
- jsフォルダの中にmain.jsを作成し以下のように記述
// 👇 自分の MockAPI の URL に書き換える
const BASE_URL = "https://69368643f8dc350aff312a6c.mockapi.io/quotes";
const statusEl = $("#status");
const tbody = $("#quotesBody");
const form = $("#quoteForm");
const quoteIdInput = $("#quoteId");
const jpInput = $("#jp");
const enInput = $("#en");
const descInput = $("#description");
const resetBtn = $("#resetBtn");
const submitBtn = $("#submitBtn");
//DOM要素取得用のショートハンド関数
function $(selector){
return document.querySelector(selector);
}
// HTML エスケープ
function escapeHtml(str="") {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// ステータス表示
function setStatus(message) {
statusEl.textContent = message;
}
// 一覧取得
async function loadQuotes() {
setStatus("読み込み中...");
try {
const resp = await fetch(BASE_URL);
if (!resp.ok) throw new Error("HTTP " + resp.status);
const data = await resp.json();
renderTable(data);
setStatus("読み込み完了 (" + data.length + "件)");
} catch (e) {
console.error(e);
setStatus("読み込みに失敗しました: " + e.message);
}
}
// テーブル描画
function renderTable(quotes) {
tbody.innerHTML = "";
quotes.forEach(q => {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${q.id}</td>
<td>${escapeHtml(q.jp)}</td>
<td>${escapeHtml(q.en)}</td>
<td>${escapeHtml(q.description)}</td>
<td class="actions"></td>
`;
const actionsTd = tr.querySelector(".actions");
const editBtn = document.createElement("button");
editBtn.textContent = "編集";
editBtn.className = "btn-secondary";
editBtn.onclick = () => startEdit(q);
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "削除";
deleteBtn.className = "btn-danger";
deleteBtn.onclick = () => deleteQuote(q.id);
actionsTd.appendChild(editBtn);
actionsTd.appendChild(deleteBtn);
tbody.appendChild(tr);
});
}
// 編集開始
function startEdit(q) {
quoteIdInput.value = q.id;
jpInput.value = q.jp || "";
enInput.value = q.en || "";
descInput.value = q.description || "";
submitBtn.textContent = "更新する";
setStatus("ID " + q.id + " を編集中");
}
// フォームリセット
function resetForm() {
quoteIdInput.value = "";
jpInput.value = "";
enInput.value = "";
descInput.value = "";
submitBtn.textContent = "新規作成";
setStatus("新規作成モード");
}
resetBtn.addEventListener("click", resetForm);
// 作成 or 更新
form.addEventListener("submit", async (e) => {
// フォームのデフォルト送信(ページ遷移)を無効化
e.preventDefault();
const payload = {
jp: jpInput.value.trim(),
en: enInput.value.trim(),
description: descInput.value.trim()
};
const id = quoteIdInput.value;
// 文字列 id を真偽値に変換("" → false, "3" → true)
const isUpdate = !!id;
try {
setStatus(isUpdate ? "更新中..." : "作成中...");
const resp = await fetch(isUpdate ? `${BASE_URL}/${id}` : BASE_URL, {
method: isUpdate ? "PUT" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!resp.ok) throw new Error("HTTP " + resp.status);
await loadQuotes();
resetForm();
setStatus(isUpdate ? "更新しました" : "作成しました");
} catch (e2) {
console.error(e2);
setStatus("保存に失敗しました: " + e2.message);
}
});
// 削除
async function deleteQuote(id) {
if (!confirm(`ID ${id} を削除しますか?`)) return;
try {
setStatus("削除中...");
const resp = await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
if (!resp.ok) throw new Error("HTTP " + resp.status);
await loadQuotes();
setStatus("削除しました");
} catch (e) {
console.error(e);
setStatus("削除に失敗しました: " + e.message);
}
}
// 初期表示
loadQuotes();ポイント解説①:BASE_URL と API 設定
const BASE_URL = "https://xxxx.mockapi.io/quotes";この定数には、アクセス先の Web API の URLを定義しています。
- 一覧取得(GET)
- 新規作成(POST)
- 更新(PUT)
- 削除(DELETE)
すべての通信は、この BASE_URL を基準に行われます。
👉 自分の MockAPI の URL に書き換えるだけで動く設計にしているのがポイントです。
ポイント解説②:DOM 取得用のショートハンド関数 $()
function $(selector){
return document.querySelector(selector);
}document.querySelector() を短く書くための
ユーティリティ(ショートハンド)関数です。
const statusEl = $("#status");のように、jQuery 風の書き方ができるため、
- コード量が減る
- DOM 操作が読みやすくなる
というメリットがあります。
👉 機能を増やさず、可読性だけを高める関数です。
ポイント解説③:HTML エスケープ処理(XSS 対策)
function escapeHtml(str="") {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}API から取得した文字列をinnerHTML で画面に描画する前にエスケープしています。
<script>などがそのまま実行されるのを防止- ユーザー入力を扱う場合の基本対策
👉 教材レベルでも入れておくと安心な防御コードです。
ポイント解説④:async / await を使った一覧取得
async function loadQuotes() {
const resp = await fetch(BASE_URL);
const data = await resp.json();
renderTable(data);
}fetch()は Promise を返すawaitによって 通信完了を待ってから次の処理へ進む
その結果、
- コールバック地獄にならない
- 同期処理のように直感的に書ける
👉 現代 JavaScript の非同期処理の基本形です。
ポイント解説⑤:DOM を直接生成してテーブルを描画
const tr = document.createElement("tr");
tr.innerHTML = `...`;
tbody.appendChild(tr);フレームワークを使わず、
- DOM を生成
- innerHTML で内容を埋める
- appendChild で配置
という 素の JavaScript の王道パターンで実装しています。
👉 Vue / React の「再描画」の仕組みの原型とも言える考え方です。
ポイント解説⑥:クロージャを使ったイベント登録
editBtn.onclick = () => startEdit(q);
deleteBtn.onclick = () => deleteQuote(q.id);forEach 内の q を
アロー関数のクロージャとして保持しています。
- ボタンが押された「未来」でも
- 対応する
qのデータに正しくアクセスできる
👉 イベント駆動 UI の重要な基礎概念です。
ポイント解説⑦:フォーム送信を JavaScript で制御
e.preventDefault();form 本来の動作である
- ページ遷移
- 再読み込み
をキャンセルし、
JavaScript 側で通信・画面更新を制御しています。
👉 ページ遷移しない CRUD アプリの基本形です。
ポイント解説⑧:新規作成か更新かの判定
const isUpdate = !!id;""→ false"3"→ true
という JavaScript の真偽値変換を利用しています。
method: isUpdate ? "PUT" : "POST"👉 1つのフォームで「作成」と「更新」を切り替える定番テクニックです。
ポイント解説⑨:初期表示で一覧を読み込む
loadQuotes();ページ読み込み時に一度だけ API を呼び、
- 一覧を取得
- テーブルを描画
します。
👉 SPA(シングルページアプリ)らしい初期化処理です。
まとめ
この JavaScript は、
- 非同期通信(fetch / async / await)
- DOM 操作
- イベント処理
- 簡易セキュリティ対策
を フレームワークなしで一通り体験できる構成になっています。
「Vue / React を使う前に、
まず素の JavaScript で仕組みを理解する」
という目的に非常に向いたサンプルです 👍
実行
- type=”module”を使っているのでファイルをブラウザにドラッグ&ドロップというわけにはいきません。Webサーバーに配置して実行します。ここではvscodeから簡単に実行できる
Live Serverを使っていきます。(Live Serverを入れるのは->こちら) - vscodeで開き、Open with Live Serverを押す

- 以下のように表示されれば成功です

最後に
作成、更新、削除などをしてみましょう。非同期通信によるページ遷移のない快適な操作ができるはずです。
今回作成した処理はReactやVueといったJSフレームワークの土台となる考え方です。繰り返し作成し、慣れていくとよいでしょう。

コメント