Drizzle orm でcreatedAtとupdatedAtを自動設定する
投稿日: 2025/07/03
DrizzleでもrailsなどのようにcreatedAtやupdatedAtを自動的に設定したい。
今回はSQLiteもしくはCloudflare d1を使用しているとする。
結論
.$defaultFn
や.$onUpdateFn
を使ってアプリケーション側で日時を設定する方法が個人的に良さそうであった。
DB側でSQLを使って更新する方法が検索するとよく見つかったが、自動更新はSQLでカラムに値を入れて、手動で更新時はJS(TS)でカラムに値を入れるみたいなことをするのは2重管理のようで面倒に感じた。
SQLiteは動的な型システムを採用していて異なる型の値を入れてもエラーにならないことがあるので、アプリケーション側からの書き込みに統一したほうが整合性取りやすいと思う。
カラムがtext型のパターン
日時のカラムの型はtext型とinteger型の2パターン選択肢がある。
個人的にはtext型のほうが好み。DBを見たときにわかりやすいし、ソートも問題なくできる。ソートのパフォーマンスはinteger型のほうがいいかもしれないが、計測したことはない。
DrizzleでDateをtextカラムに自動変換してくれる機能はデフォルトでないため、customTypeで作成する必要がある。
issueは経っているが、公式での対応はしばらくなさそうである。
const isoDateTime = t.customType<{ data: Date // type of TypeScript driverData: string // type of DB }>({ dataType: (): string => "text", // type of DB toDriver: (value: Date): string => value.toISOString(), // TypeScript -> DB fromDriver: (value: string): Date => new Date(value), // DB -> TypeScript }) const timestamps = { createdAt: isoDateTime("created_at") .notNull() .$defaultFn(() => new Date()), updatedAt: isoDateTime("updated_at") .notNull() .$defaultFn(() => new Date()) .$onUpdateFn(() => new Date()), } export const posts = t.sqliteTable( "posts", { id: t.text().primaryKey(), slug: t.text().notNull().unique(), ...timestamps, }, )
カラムがinteger型のパターン
integer型のときはmode: "timestamp_ms"
を指定することでDate型を自動的に変換してくれる。
integer型でカラムに数字が入る。
const timestamps = { createdAt: t .integer("created_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()), updatedAt: t .integer("updated_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()) .$onUpdateFn(() => new Date()), } export const posts = t.sqliteTable( "posts", { id: t.text().primaryKey(), slug: t.text().notNull().unique(), ...timestamps, }, )
SQLiteではイマイチだった方法
個人的に上記の方法がより良いと感じただけで、当然この実装も問題はない。
sql(CURRENT_TIMESTAMP)
.default(sql`(CURRENT_TIMESTAMP)`)
公式ドキュメントではこの方法が紹介されている。
カラムのtypeがtextのときに使えます。integerのカラムで使うと、読み込み時にInvalidDateになります。
SQLiteはintegerカラムに文字列を入れてもエラーになりません。
sql(strftime('%s', 'now'))
.default(sql`(strftime('%s', 'now'))`)
問題なさそうではあるが、DB側で値が設定されアプリケーション側から.set
メソッドで日時を設定するときと異なる方法になってしまう。
常にアプリケーション側で設定したほうが管理が楽だと思う。