【Drizzle ORM シリーズ】
・Drizzle ORM を Next.js で使ってみる #Next.js - Qiita
https://qiita.com/sigeta/items/c0a2b0463eca06314906
・Drizzle ORM でスキーマファイルを使ってDBを操作する方法 #Next.js - Qiita
https://qiita.com/sigeta/items/99a625c17c6a0a75da54
・Drizzle ORM でリレーションを張る方法 #Next.js - Qiita
https://qiita.com/sigeta/items/b800f3e38beb1639d2b7
・Drizzle ORM の設定ファイルを接続時も利用する方法 #TypeScript - Qiita
https://qiita.com/sigeta/items/d9267a06061d4d2a51e8
・Drizzle ORM でダミーデータを流し込む方法 #TypeScript - Qiita
https://qiita.com/sigeta/items/8bc85b74273eeb37c27a
Drizzle ORM
多機能だから抑えるの大変だね!
今日はリレーション系。
その前に Debug モード
DB接続時の設定で logger: true
を指定すると、発行SQLがコンソールに出力される。
便利なので、開発時は有効にしても良いかもしれない。
import { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'
import Database from 'better-sqlite3'
import * as schema from './schema'
import process from 'node:process'
process.loadEnvFile()
const sqlite = new Database(process.env.DATABASE_URL!)
export const db: BetterSQLite3Database<typeof schema> = drizzle(sqlite, {
schema,
casing: 'snake_case',
logger: true, // SQLログ出力
})
drizzle.config.ts
はdrizzle-kit
のみで使われるらしい。
コード内で実行するときは利用されないので、何か手法を考えてみたい。
Soft Relation
あまり聞きなれない用語だが、要するに外部キーを使わないリレーション。
これはDB層ではなく、アプリケーション層でリレーションを構築することで柔軟に運用できる。
既存の大きいDBをラップしたり、リレーションに対応していないDBも扱えそう。
一対一、一対多、多対多に対応している。
前の記事で作った users に posts を追加する。
一人(user)は複数の記事(post)を持つ。
import { relations, sql } from 'drizzle-orm'
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer().notNull().primaryKey(),
name: text().notNull(),
age: integer(),
createdAt: integer({ mode: 'timestamp' }).notNull()
.default(sql`(unixepoch())`),
updatedAt: integer({ mode: 'timestamp' }).notNull()
.default(sql`(unixepoch())`)
.$onUpdate(() => sql`(unixepoch())`),
})
export const posts = sqliteTable('posts', {
id: integer().notNull().primaryKey(),
userId: integer().notNull(), // Foreign Key
title: text().notNull(),
body: text(),
createdAt: integer({ mode: 'timestamp' }).notNull()
.default(sql`(unixepoch())`),
updatedAt: integer({ mode: 'timestamp' }).notNull()
.default(sql`(unixepoch())`)
.$onUpdate(() => sql`(unixepoch())`),
})
// リレーションの定義
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
user: one(users, {
fields: [posts.userId],
references: [users.id],
}),
}))
こんな感じで relatioins
を使って双方向に定義するみたい。
userId: integer().notNull(), // Foreign Key
と書いているが、
drizzle/sql
を確認すると、Foreign Key は付与されていない。db/drizzle/0001_same_captain_cross.sqlCREATE TABLE `posts` ( `id` integer PRIMARY KEY NOT NULL, `user_id` integer NOT NULL, `title` text NOT NULL, `body` text, `created_at` integer DEFAULT (unixepoch()) NOT NULL, `updated_at` integer DEFAULT (unixepoch()) NOT NULL ); --> statement-breakpoint CREATE TABLE `users` ( `id` integer PRIMARY KEY NOT NULL, `name` text NOT NULL, `age` integer, `created_at` integer DEFAULT (unixepoch()) NOT NULL, `updated_at` integer DEFAULT (unixepoch()) NOT NULL );
作ったら DB と同期しておく。
$ npx drizzle-kit generate
$ npx drizzle-kit push
リレーションの取得方法
ではこのデータを取得してみる。
ChatGPT くんに適当にテストデータを作ってもらった。
それを npx drizzle-orm studio
で登録する。
'use server'
import { db } from '@/db'
import { posts, users } from '@/db/schema'
import { asc, desc, eq, like, and } from 'drizzle-orm'
export const getUsers = async () => {
const resultA = await db.query.users
.findMany({
where: and(
like(users.name, '%中%'),
),
orderBy: asc(users.id),
with: {
posts: {
where: like(posts.title, '%の%'),
orderBy: desc(posts.id),
},
},
})
const resultB = (
await db.select()
.from(posts)
.leftJoin(users, eq(users.id, posts.userId))
.where(and(
like(users.name, '%中%'),
like(posts.title, '%の%')
))
.orderBy(asc(users.id))
)
return {
resultA,
resultB,
}
}
resultA
結果はこんな感じ!
resultA
はいわゆるビルダー形式での検索。
json クエリを多用して、一回のSQL発行で取得していた。
(Laravel などとは異なるみたい)
ちなみに with に制約を設けないのであれば posts: true
とすればよい。
with: {
posts: true,
},
"resultA": [
{
"id": 1,
"name": "中島",
"age": 27,
"createdAt": "2025-03-23T05:32:58.000Z",
"updatedAt": "2025-03-23T05:32:58.000Z",
"posts": [
{
"id": 10,
"userId": 1,
"title": "雨の日の楽しみ方",
"body": "雨の日はお気に入りのレインコートを着て、静かなカフェで過ごすのが最近のマイブーム。",
"createdAt": "2025-03-23T05:47:46.000Z",
"updatedAt": "2025-03-23T05:47:46.000Z"
},
resultB
resultB
は素のSQL形式での検索。
leftJoin
などやりたいことは一通りできそう。
map
や filter
を駆使して、with
を実現することもできる。
デフォルトだと、モデルごとにまとめて json に加工してくれる。
これが面倒な場合は、必要なカラムを手動で指定できる。
ちなみに select にて posts のカラムだけ取得する場合は、短縮メソッドが用意されている。
select({
...getTableColumns(posts),
author: users.name,
})
"resultB": [
{
"posts": {
"id": 1,
"userId": 1,
"title": "春の訪れとともに",
"body": "今日は近くの公園に行ったら、桜が咲き始めていました。まだ五分咲きでしたが、春が近づいてきているのを実感しました。",
"createdAt": "2025-03-23T05:47:46.000Z",
"updatedAt": "2025-03-23T05:47:46.000Z"
},
"users": {
"id": 1,
"name": "中島",
"age": 27,
"createdAt": "2025-03-23T05:32:58.000Z",
"updatedAt": "2025-03-23T05:32:58.000Z"
}
},
Relation
DB に ForeignKey
のリレーションを付与する、よくあるやつ。
これは references()
というメソッドを利用する。
export const posts = sqliteTable('posts', {
id: integer().notNull().primaryKey(),
- userId: integer().notNull(), // Foreign Key
+ userId: integer().notNull().references(() => users.id), // Foreign Key
title: text().notNull(),
とても簡単。
この状態で drizzle-kit generate
を行うと、ちゃんとリレーションが付く。
$ npx drizzle-kit generate
$ npx drizzle-kit push
Drizzle はいったん削除して、データを置き換える形でマイグレーションを行うみたい。
ALTER
で実装すると汚くなるから良い実装かもしれない。db/drizzle/0002_needy_pestilence.sqlPRAGMA foreign_keys=OFF;--> statement-breakpoint CREATE TABLE `__new_posts` ( `id` integer PRIMARY KEY NOT NULL, `user_id` integer NOT NULL, `title` text NOT NULL, `body` text, `created_at` integer DEFAULT (unixepoch()) NOT NULL, `updated_at` integer DEFAULT (unixepoch()) NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action ); --> statement-breakpoint INSERT INTO `__new_posts`("id", "user_id", "title", "body", "created_at", "updated_at") SELECT "id", "user_id", "title", "body", "created_at", "updated_at" FROM `posts`;--> statement-breakpoint DROP TABLE `posts`;--> statement-breakpoint ALTER TABLE `__new_posts` RENAME TO `posts`;--> statement-breakpoint PRAGMA foreign_keys=ON;
データの取得などは先ほどと同様。
安全に使うのならば Foreign Key
を指定したほうが良いが、丁寧に実装しないといけないのでケースバイケースにはなると思う。
(大昔使わずにシステムを作成したが、特に大きな影響は出なかった。indexは張ろう)
続き
Drizzle ORM の設定ファイルを接続時も利用する方法 #TypeScript - Qiita
https://qiita.com/sigeta/items/d9267a06061d4d2a51e8