小さいWebアプリを2つ作りました。
1つ目は、一人用カードゲーム「アリスの時間迷宮」を遊ぶときに使うCounterです。プレイ中の数値を手元で動かせればよかったので、機能は増減、リセット、カウンターの追加と削除に絞りました。
2つ目はKakeiboです。市販の家計簿アプリほど多機能でなくていいので、日付、カテゴリ、金額、メモだけを入れられるものが欲しかったのと、PocketBaseを実際に触ってみたかったのがきっかけです。
「アリスの時間迷宮」用のCounter
CounterはViteとVanilla TypeScriptで作りました。Reactは使わず、CounterクラスがDOMを組み立て、値もクラスの中で持っています。

最初の1個を表示したあと、ADD COUNTERを押すたびに同じクラスのインスタンスを追加します。
const initialCounter = new Counter('カウンター', 0);countersContainer.appendChild(initialCounter.render());
addButton.addEventListener('click', () => { const newCounter = new Counter('カウンター', 0); countersContainer.appendChild(newCounter.render());});Counterが持つ状態は名前と数値の2つだけです。
export class Counter { private label: string; private value: number;
constructor(label: string, initialValue: number = 0) { this.label = label; this.value = initialValue; }
private updateDisplay() { this.displayEl.textContent = this.value.toString(); }}画面には1ずつ増減する大きなボタンに加え、-5、-2、+2、+5を置きました。各ボタンはvalueを変えてから表示を更新します。
this.containerEl.querySelector('.minus-btn') ?.addEventListener('click', () => { this.value -= 1; this.updateDisplay(); });
this.containerEl.querySelector('.adjust-plus-5') ?.addEventListener('click', () => { this.value += 5; this.updateDisplay(); });
this.containerEl.querySelector('.reset-btn') ?.addEventListener('click', () => { this.value = 0; this.updateDisplay(); });カウンター名はカード上部の入力欄で変更できます。不要になったカードは左上のボタンでDOMから削除します。画面幅に応じて1〜4列へ変わるグリッドにしたので、複数の数値を並べても同じ画面で確認できます。
Counterの実装の流れ
Counter側の実装は次の5段階に分かれています。
- Vite、TypeScript、Tailwind CSSだけの画面を用意する
Counterクラスにlabelとvalueを持たせる- 増減、リセット、名前変更、削除のイベントを付ける
main.tsで初期カウンターと追加ボタンを配置する- CSS Gridの列数を画面幅で切り替える
Counterはまだ保存しない
現在のCounterにはlocalStorageもデータベースもありません。数値と名前はCounterインスタンスの中だけにあるため、ページを再読み込みすると初期状態へ戻ります。ログイン機能もありません。
「アリスの時間迷宮」を遊んでいる間だけ使う道具としてはこれで足りますが、途中状態から再開する用途にはまだ対応していません。
PocketBaseを試すために作ったKakeibo
KakeiboはReactのフロントエンドとPocketBaseのバックエンドに分けました。画面から登録した支出はPocketBaseへ保存し、表示中の月を変えると、その月のデータを取り直します。

プロジェクトの構成は次のようになっています。
kakeibo/├── compose.yaml├── pb_migrations/│ └── 1690000000_init_transactions.js├── pb_data/ # 起動後に作られる保存先└── frontend/ └── src/ ├── App.tsx ├── components/ └── lib/pocketbase.tsPocketBaseは0.22.10を起動時に取得し、データをホスト側のpb_dataへ保存しています。フロントエンドから接続先を指定しなかった場合は、ブラウザで開いているホスト名の8090番ポートへ接続します。
const baseUrl = import.meta.env.VITE_POCKETBASE_URL || `${window.location.protocol}//${window.location.hostname}:8090`;
export const pb = new PocketBase(baseUrl);transactionsコレクション
最初の起動時にmigrationからtransactionsコレクションを作ります。フィールドは次の5つです。
| フィールド | 型 | 現在の使い方 |
|---|---|---|
type | select | expenseまたはincome |
date | date | 支出した日 |
category | text | 食費、日用品、交通費、住居費、交際費、その他 |
amount | number | 0以上の整数 |
memo | text | 任意の短いメモ |
モデル上は収入も持てますが、現在の登録画面はexpenseを固定で送っています。収入入力はまだ作っていません。
await pb.collection('transactions').create({ type: 'expense', date: date + ' 00:00:00.000Z', category, amount: Number(amount), memo,});保存に成功したら金額とメモを空にし、一覧を再取得します。履歴側には削除ボタンもあり、確認ダイアログのあとにレコードIDを指定して削除します。
await pb.collection('transactions').delete(id);onDelete(); // 一覧を再取得月ごとの取得と集計
App.tsxでは表示中の月の開始日時と終了日時を作り、PocketBaseのfilterへ渡しています。日付の新しい順で最大100件を取得する実装です。
const records = await pb.collection('transactions') .getList<Transaction>(1, 100, { sort: '-date', filter: `date >= "${startOfMonth.replace('T', ' ')}" && date <= "${endOfMonth.replace('T', ' ')}"`, });
setTransactions(records.items);取得したレコードは日付ごとにまとめて履歴へ表示します。今月の総支出とカテゴリ別の金額は、React側でtransactionsをreduceして計算しています。PocketBase側に集計用のAPIは追加していません。
100件を超えた分のページングも未実装なので、ひと月の登録数が多い場合は続きを取得できません。
Kakeiboの実装の流れ
Kakeibo側の実装は次の6段階に分かれています。
- PocketBaseのmigrationで
transactionsを定義する - React側にPocketBaseクライアントを用意する
- 支出フォームから
createする - 月の範囲を指定して一覧を取得する
- 履歴の削除と、カテゴリ別集計を追加する
pb_dataをホストへマウントして再起動後も記録を残す
保存と認証の現在地
2つのアプリで保存方法ははっきり分かれています。
- Counterはブラウザのメモリだけを使い、再読み込みで消える
- KakeiboはPocketBaseへ保存し、
pb_dataにデータベースを残す
Kakeiboには、まだユーザーログインを付けていません。フロントエンドにも認証処理はなく、migrationのlistRule、viewRule、createRule、updateRule、deleteRuleはすべて空文字です。現在は未認証アクセスを許可する設定なので、そのままインターネットへ公開する構成ではありません。
"listRule": "","viewRule": "","createRule": "","updateRule": "","deleteRule": "",つまり、今のKakeiboは自分の端末や閉じた開発環境で試す段階です。外部公開するなら、先にPocketBaseのユーザー認証とコレクションルールを追加し、各レコードを所有者へ結び付ける必要があります。
Counterは目的を満たすところまで、KakeiboはPocketBaseへの登録・月別取得・削除・集計まで作りました。次に触るなら、Counterは途中状態の保存、Kakeiboは認証と100件以降のページングから手を入れます。