この7日間プランで作る家計簿アプリのゴール
この 7 日間では、ブラウザ上で動く「シンプル家計簿アプリ」を作ります。
機能は次のようなイメージです。
- 日付・内容・金額・種別(収入 / 支出)を入力して追加できる
- 下に収支のリストが表示される
- 合計収入・合計支出・収支(差額)が表示される
- ざっくりしたグラフ(棒グラフ風)で支出カテゴリの割合を見る
HTML と CSS は最低限にしつつ、JavaScript で「配列・オブジェクト・合計計算・DOM 操作・簡単なグラフ描画・ローカルストレージ」を体験するのが狙いです。
1日目:土台の画面を作る(入力欄とリストの枠)
フォルダとHTMLの準備
- デスクトップなどに「kakeibo-app」というフォルダを作る。
- その中に「index.html」を作る。
- ブラウザで「index.html」を開けることを確認する。
ここまでは、今までのアプリ学習と同じ流れです。
家計簿画面の骨組みを書く
「入力欄」「合計表示」「リスト」「グラフエリア」の枠だけ用意します。
index.html に次の内容を貼り付けてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>家計簿アプリ</title>
<style>
body {
font-family: sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 16px;
border-radius: 8px;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
margin-top: 0;
}
.input-area {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.input-area input,
.input-area select {
padding: 4px;
font-size: 14px;
}
.input-area button {
padding: 6px 10px;
font-size: 14px;
}
.summary {
margin-bottom: 16px;
border-bottom: 1px solid #ddd;
padding-bottom: 8px;
}
.summary span {
margin-right: 16px;
font-weight: bold;
}
.list-header,
.list-row {
display: grid;
grid-template-columns: 100px 1fr 100px 80px 80px;
gap: 8px;
padding: 4px 0;
border-bottom: 1px solid #eee;
font-size: 14px;
}
.list-header {
font-weight: bold;
border-bottom: 2px solid #ccc;
}
.list-row button {
font-size: 12px;
}
.income {
color: #2e7d32;
}
.expense {
color: #c62828;
}
.graph-area {
margin-top: 16px;
}
.bar-row {
display: flex;
align-items: center;
margin-bottom: 4px;
font-size: 13px;
}
.bar-label {
width: 80px;
}
.bar {
height: 16px;
background-color: #42a5f5;
margin-left: 4px;
}
</style>
</head>
<body>
<div class="container">
<h1>家計簿アプリ</h1>
<div class="input-area">
<input id="date-input" type="date" />
<input id="desc-input" type="text" placeholder="内容 (例: 食費, 給料)" />
<input id="amount-input" type="number" placeholder="金額" />
<select id="type-select">
<option value="income">収入</option>
<option value="expense">支出</option>
</select>
<button id="add-button">追加</button>
</div>
<div class="summary">
<span id="income-total">合計収入: 0 円</span>
<span id="expense-total">合計支出: 0 円</span>
<span id="balance">収支: 0 円</span>
</div>
<div class="list-header">
<div>日付</div>
<div>内容</div>
<div>金額</div>
<div>種別</div>
<div>操作</div>
</div>
<div id="list-body">
<!-- ここに収支の行が追加される -->
</div>
<div class="graph-area">
<h3>カテゴリ別支出(ざっくり棒グラフ)</h3>
<div id="graph-body">
<!-- 棒グラフをここに描画 -->
</div>
</div>
</div>
<script>
console.log("家計簿アプリスタート");
</script>
</body>
</html>
ブラウザで表示し、入力欄と表のヘッダー、グラフの見出しが出ていれば 1 日目は完了です。
2日目:1件のデータ構造(オブジェクト)と追加処理の流れを理解する
家計簿1行をオブジェクトで表現する(重要)
家計簿の1行は、次のような情報を持っています。
- date(日付、文字列)
- desc(内容、文字列)
- amount(金額、数値)
- type(収入か支出か、”income” / “expense”)
JavaScript では、1 行をオブジェクトで表現します。
const record = {
date: "2025-01-01",
desc: "給料",
amount: 300000,
type: "income"
};
JavaScriptポイントは、
「配列は“順番重視の箱”」「オブジェクトは“項目名付きの箱”」というイメージです。
家計簿1行のように「項目名がはっきりしているデータ」にはオブジェクトが向いています。
すべてのデータは配列でまとめる
家計簿は複数行になります。
これを配列でまとめて管理します。
let records = [];
JavaScript新しいレコードを追加するときは、push で配列に足します。
records.push(record);
JavaScriptこの「配列の中にオブジェクトが並んでいる」構造は、現場でも頻出です。
家計簿だけでなく、ユーザー一覧、商品一覧、TODO リストなど、多くのデータが同じ形で管理されます。
DOM 要素を取得する
script タグの中に、入力欄などを取得するコードを書きます。
const dateInput = document.getElementById("date-input");
const descInput = document.getElementById("desc-input");
const amountInput = document.getElementById("amount-input");
const typeSelect = document.getElementById("type-select");
const addButton = document.getElementById("add-button");
const listBody = document.getElementById("list-body");
const incomeTotalSpan = document.getElementById("income-total");
const expenseTotalSpan = document.getElementById("expense-total");
const balanceSpan = document.getElementById("balance");
const graphBody = document.getElementById("graph-body");
let records = [];
JavaScriptまずは「JavaScript から画面の部品を掴めているか」を確認するのが大事です。
コンソールに console.log で出してみてもいいです。
追加処理の骨組みを作る
まずは「追加ボタンを押したら、入力された値を取り出す」ところまでやります。
addButton.addEventListener("click", function() {
const date = dateInput.value;
const desc = descInput.value.trim();
const amount = Number(amountInput.value);
const type = typeSelect.value;
console.log(date, desc, amount, type);
});
JavaScriptここで大事なのは、
文字列としての入力を Number(...) で数値に変換していることです。
家計簿の合計を計算するときは「数値」でなければならないので、ここで変換しておきます。
3日目:入力値からrecordオブジェクトを作り、リストに1行表示する
入力値のバリデーション(最低限)
いきなり何でも受け入れるとバグりやすいので、
最低限のチェックを入れます。
- 日付が空でないか
- 内容(desc)が空でないか
- 金額が 0 以上か
追加ボタンのイベント内を次のようにします。
addButton.addEventListener("click", function() {
const date = dateInput.value;
const desc = descInput.value.trim();
const amount = Number(amountInput.value);
const type = typeSelect.value;
if (!date) {
alert("日付を入力してください");
return;
}
if (!desc) {
alert("内容を入力してください");
return;
}
if (!amount || amount <= 0) {
alert("金額は 1 以上の数値を入力してください");
return;
}
const record = {
id: Date.now(), // 簡易なユニークID
date: date,
desc: desc,
amount: amount,
type: type
};
records.push(record);
descInput.value = "";
amountInput.value = "";
renderList();
renderSummary();
renderGraph();
});
JavaScriptここで new なのは「id」というプロパティです。
Date.now() は現在時刻のミリ秒を返すので、簡易的なユニーク ID としてよく使われます。
削除する時にどのレコードかを特定するために、この id が役立ちます。
1行分のDOM要素を作る関数(重要)
records の 1 要素から、画面に表示する 1 行を作る関数を用意します。
function createRow(record) {
const row = document.createElement("div");
row.className = "list-row";
const dateDiv = document.createElement("div");
dateDiv.textContent = record.date;
const descDiv = document.createElement("div");
descDiv.textContent = record.desc;
const amountDiv = document.createElement("div");
amountDiv.textContent = record.amount.toLocaleString() + " 円";
if (record.type === "income") {
amountDiv.classList.add("income");
} else {
amountDiv.classList.add("expense");
}
const typeDiv = document.createElement("div");
typeDiv.textContent = record.type === "income" ? "収入" : "支出";
const actionsDiv = document.createElement("div");
const deleteButton = document.createElement("button");
deleteButton.textContent = "削除";
actionsDiv.appendChild(deleteButton);
deleteButton.addEventListener("click", function() {
deleteRecord(record.id);
});
row.appendChild(dateDiv);
row.appendChild(descDiv);
row.appendChild(amountDiv);
row.appendChild(typeDiv);
row.appendChild(actionsDiv);
return row;
}
JavaScriptここは家計簿アプリの「心臓部の1つ」なので少し深掘りします。
- record はオブジェクトで、そのプロパティを使って表示を作っている。
- row の中に div を複数 appendChild して「1 行の見た目」を構成している。
- deleteButton のイベントの中から、外側の record.id を使って削除処理を呼び出している。
この「オブジェクト → DOM」の変換は、どんなアプリでもほぼ必ず出てくるパターンです。
records全体を画面に描画するrenderList
配列 records の中身を、全行描画する関数を作ります。
function renderList() {
listBody.textContent = "";
records.forEach(function(record) {
const row = createRow(record);
listBody.appendChild(row);
});
}
JavaScript重要ポイントは、「一度全部消してから全部描き直す」戦略です。
細かく差分更新するより、初心者のうちはこの方式の方が理解しやすくバグも少ないです。
4日目:削除機能と合計の計算(収入・支出・収支)
削除処理deleteRecordの実装(重要)
3 日目に出てきた deleteRecord 関数を実装します。
function deleteRecord(id) {
records = records.filter(function(record) {
return record.id !== id;
});
renderList();
renderSummary();
renderGraph();
}
JavaScriptここでは、filter を使って「指定した id 以外のものだけ残す」形で配列を再構築しています。
結果として、削除対象のレコードだけが配列から消えます。
filter は「条件に合う要素だけを残して新しい配列を作る」関数です。
家計簿だけでなく、検索や絞り込みなどにも広く使われます。
合計収入・合計支出・収支の計算
合計計算用の関数 renderSummary を作ります。
function renderSummary() {
let incomeTotal = 0;
let expenseTotal = 0;
records.forEach(function(record) {
if (record.type === "income") {
incomeTotal += record.amount;
} else {
expenseTotal += record.amount;
}
});
const balance = incomeTotal - expenseTotal;
incomeTotalSpan.textContent = "合計収入: " + incomeTotal.toLocaleString() + " 円";
expenseTotalSpan.textContent = "合計支出: " + expenseTotal.toLocaleString() + " 円";
balanceSpan.textContent = "収支: " + balance.toLocaleString() + " 円";
}
JavaScriptこの関数で大事なポイントを整理します。
- 配列をループしながら、条件によって違う変数に加算している。
収入だけ incomeTotal に、支出だけ expenseTotal に足している。 - toLocaleString() を使って、3 桁区切りの見やすい形式にしている。
- 収支(差額)は「合計収入 − 合計支出」で計算している。
これで、追加・削除のたびに renderSummary を呼び出すことで、合計が自動更新されます。
5日目:カテゴリ別支出の集計と簡易棒グラフの描画
カテゴリをどう扱うか
今の仕様では「desc(内容)」に「食費」「家賃」などを書いてもらっています。
シンプルにするため、「desc をカテゴリとして扱う」というルールにします。
例えば、内容が
「食費 牛丼」「家賃 1月」「光熱費 電気」
のように混ざると集計がややこしくなるので、最初は「純粋にカテゴリ名を入れる」と仮定して進めます。
慣れてきたら「カテゴリ選択のプルダウン+メモ欄」などに発展させるとよいです。
カテゴリごとに支出を集計する(重要)
支出のみを対象に、「カテゴリ → 合計支出」のマップを作ります。
JavaScript のオブジェクトを使って、カテゴリ名をキーにします。
function calcExpenseByCategory() {
const categoryMap = {}; // 例: { "食費": 15000, "家賃": 60000 }
records.forEach(function(record) {
if (record.type !== "expense") {
return;
}
const cat = record.desc;
if (!categoryMap[cat]) {
categoryMap[cat] = 0;
}
categoryMap[cat] += record.amount;
});
return categoryMap;
}
JavaScriptこの処理はデータ集計の基本的パターンです。
- 空のオブジェクトを用意する。
- ループしながら、キーが存在しない場合は 0 で初期化。
- 金額を加算していく。
このロジックが理解できると、売上集計やアクセス解析など、いろいろな集計処理に応用できます。
棒グラフ風の表示を作る
グラフといっても、最初は「横長の棒の長さだけ変える」くらいで十分です。
renderGraph 関数を作ります。
function renderGraph() {
graphBody.textContent = "";
const categoryMap = calcExpenseByCategory();
const categories = Object.keys(categoryMap);
if (categories.length === 0) {
graphBody.textContent = "支出データがありません。";
return;
}
const maxValue = Math.max.apply(null, categories.map(function(cat) {
return categoryMap[cat];
}));
categories.forEach(function(cat) {
const amount = categoryMap[cat];
const row = document.createElement("div");
row.className = "bar-row";
const label = document.createElement("div");
label.className = "bar-label";
label.textContent = cat;
const bar = document.createElement("div");
bar.className = "bar";
const widthPercent = (amount / maxValue) * 100;
bar.style.width = widthPercent + "%";
bar.title = amount.toLocaleString() + " 円";
row.appendChild(label);
row.appendChild(bar);
graphBody.appendChild(row);
});
}
JavaScriptここでの大事なポイントを整理します。
- categoryMap から keys をとって、カテゴリの配列を作っている。
- maxValue を求めて、「一番大きなカテゴリを 100% の長さ」にしている。
- 各カテゴリの棒は、maxValue に対する割合で幅を決める。
例えば、最大 50000 円、あるカテゴリが 25000 円なら幅は 50%。 - bar.title に金額を入れておくと、マウスを乗せたときにツールチップで値が見える。
これで、「支出カテゴリのバランス」がひと目で分かる簡易グラフになります。
6日目:データの保存(localStorage)と初期表示
ブラウザにデータを保存するlocalStorageの基本
localStorage は、ブラウザに小さなデータを保存しておく仕組みです。
家計簿を閉じて、次に開いたときにもデータが残っていると、アプリっぽさが一気に増します。
localStorage の基本的な使い方は次の通りです。
- 保存
localStorage.setItem(“キー名”, “文字列”); - 取得
const value = localStorage.getItem(“キー名”); - 削除
localStorage.removeItem(“キー名”);
配列やオブジェクトを保存するときは、JSON.stringify と JSON.parse を使います。
records を保存・復元する関数を作る(重要)
- 保存用 saveRecords
- 読み込み用 loadRecords
この2つを用意します。
const STORAGE_KEY = "kakeibo-records";
function saveRecords() {
const json = JSON.stringify(records);
localStorage.setItem(STORAGE_KEY, json);
}
function loadRecords() {
const json = localStorage.getItem(STORAGE_KEY);
if (!json) {
records = [];
return;
}
try {
const data = JSON.parse(json);
if (Array.isArray(data)) {
records = data;
} else {
records = [];
}
} catch (e) {
console.error("データの読み込みに失敗しました", e);
records = [];
}
}
JavaScriptポイントをまとめます。
- JSON.stringify(records) で配列を文字列にして保存。
- JSON.parse(json) で文字列から元の配列にもどす。
- 読み込み失敗やフォーマット不正の可能性を考えて、try-catch で囲んでおく。
こうしておくと、もし何かの拍子に localStorage の中身がおかしくなっても、アプリ全体が落ちずに済みます。
保存と読み込みをアプリの流れに組み込む
- アプリ起動時に loadRecords してから render を呼ぶ。
- records が変わるたびに saveRecords を呼ぶ。
具体的には以下のようにします。
function init() {
loadRecords();
renderList();
renderSummary();
renderGraph();
}
init();
JavaScriptそして、
追加・削除処理の最後に saveRecords を追加します。
追加処理の最後あたりに次を足す。
saveRecords();
deleteRecord の最後にも。
function deleteRecord(id) {
records = records.filter(function(record) {
return record.id !== id;
});
renderList();
renderSummary();
renderGraph();
saveRecords();
}
JavaScriptこれで、ページをリロードしても家計簿の中身が残るようになります。
7日目:コード全体を通して理解し、自分なりの拡張を考える
完成版の全体イメージ
ここまで紹介したパーツを組み合わせたものが、家計簿アプリの完成形になります。
内容は長いので、ここでは流れだけを整理します。
- HTML で「入力欄・合計・リスト・グラフ」の枠を用意。
- JavaScript で
2-1. DOM 要素の取得
2-2. records 配列とその1要素の構造(オブジェクト)を定義
2-3. createRow / renderList で「データ → 表示」を実装
2-4. renderSummary で合計収入・支出・収支を計算
2-5. calcExpenseByCategory / renderGraph でカテゴリ別支出の集計と簡易グラフを作成
2-6. saveRecords / loadRecords で localStorage 連携
2-7. init で起動時に読み込みと描画
それぞれの関数が「何を入力にして、何を出しているか」を意識して読むと、全体の構造が理解しやすくなります。
あなたなりの拡張アイデア
この家計簿から発展させられそうなアイデアをいくつか挙げます。
- 月別のフィルター
上部に「年月選択」の入力を追加し、選択された月のデータだけ表示・集計する。 - カテゴリをプルダウンに
内容欄を「カテゴリ選択+メモ欄」に分ける。
例えば「食費」「家賃」「光熱費」「交際費」などを select 要素で選べるようにする。 - CSV 風のテキスト出力
「エクスポート」ボタンを作り、records を「日付, 内容, 金額, 種別」の形式でテキストエリアに出力する。 - 円グラフに挑戦
Canvas やすごく簡易な「絵文字グラフ」などで、円グラフっぽい表示に挑戦してみる。
どれも、今日までに学んだ「配列とオブジェクト」「ループ」「条件分岐」「DOM 操作」「イベント」「localStorage」の組み合わせで実現できます。
この家計簿アプリで身についた「本物の基礎」
- オブジェクトと配列を使った、現実世界に近いデータ構造の設計。
- render 系関数で「データと表示をきっちり分ける」考え方。
- 集計(合計・カテゴリ別集計)とグラフ化の基本パターン。
- localStorage による「ブラウザ内保存」で、アプリとしての完成度を上げる方法。

