家計簿アプリを作る #17:複数ユーザー対応
家計簿アプリ作成シリーズの第17回です。今回は複数ユーザー対応を実装します。これまで全ユーザーのデータが共有されていたため、ログインユーザーのデータのみ表示・操作できるように修正します。
実装方針
transactions テーブルに userId カラムを追加し、登録・一覧・編集・削除の各操作で userId を使ってフィルタリングします。userId には Better Auth が発行するユーザー ID(locals.user.id)を使います。
メールアドレスではなくユーザー ID を使う理由:
- メールアドレスは変更される可能性があるが、ID は変わらない
- ID はユーザーを一意に識別するために設計されている
Step 1:スキーマに userId を追加する
export const transactions = sqliteTable('transactions', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text('user_id').notNull(),
amount: int('amount').notNull(),
type: text('type').notNull(),
category: text('category').notNull(),
memo: text('memo').notNull().default(''),
date: text('date').notNull()
});
Step 2:2段階マイグレーション
既存データがある状態で NOT NULL カラムを追加するとエラーになります。以下の手順で対応します。
段階1:NULL 許容で追加する
まず userId を NULL 許容として追加するマイグレーションを作成・適用します。
段階2:既存データに userId をセットする
npx wrangler d1 execute kakeibo-app-db --local \
--command "UPDATE transactions SET user_id = 'your-user-id' WHERE user_id IS NULL"
段階3:NOT NULL に変更する
スキーマを notNull() に変更して再度マイグレーションを作成・適用します。
ハマりポイント①:誤って NOT NULL でマイグレーションファイルを作成してしまった
NOT NULL のままマイグレーションファイルを生成してしまった場合、そのファイルを直接編集して NOT NULL を削除します。余分に生成されたファイルは手動で削除します。
ハマりポイント②:_journal.json にエントリが残る
drizzle-kit generate した時点で migrations/meta/_journal.json に記録されます。適用に失敗・キャンセルしたマイグレーションファイルを削除した場合、_journal.json からも該当エントリを手動で削除する必要があります。
migrations/meta/ 配下のスナップショットファイルはそのままで問題ありません。
Step 3:登録時に userId をセットする
create: async ({ request, platform, locals }) => {
const userId = locals.user?.id;
await db.insert(transactions).values({
userId: String(userId),
// ...
});
}
Step 4:一覧・集計を userId でフィルタリングする
import { eq, like, and } from 'drizzle-orm';
// 全件取得(userId でフィルタ)
const allTransactions = await db.select().from(transactions)
.where(eq(transactions.userId, locals.user!.id));
// 月フィルタ時(and で条件を追加)
const selectedTransactions = await db
.select()
.from(transactions)
.where(
and(
eq(transactions.userId, locals.user!.id),
like(transactions.date, `${selectedMonth}%`)
)
);
複数条件の WHERE 句は and() を使います。
Step 5:削除・編集にも userId の条件を追加する
// 削除
await db.delete(transactions).where(
and(
eq(transactions.userId, locals.user!.id),
eq(transactions.id, String(id))
)
);
// 編集(load)
const transaction = await db.select().from(transactions)
.where(
and(
eq(transactions.userId, locals.user!.id),
eq(transactions.id, params.id)
)
).get();
まとめ
| ポイント | 内容 |
|---|---|
| userId | メールアドレスではなく locals.user.id を使う |
| 2段階マイグレーション | NULL 許容で追加 → データ更新 → NOT NULL に変更 |
_journal.json |
削除したマイグレーションファイルのエントリも手動で削除が必要 |
| 複数条件の WHERE | and() を使う |
| 削除・編集 | userId の条件を追加して他ユーザーのデータを操作できないようにする |