メモ:Drizzle ORMのreferencesとrelationsの違い
Published on May 7, 2025
Category: database
はじめに
TypeScriptベースのORMであるDrizzle ORMでは、テーブル間の関連付け方法としてreferences
とrelations
の2つの機能があり、これらは異なる目的と役割を持っているみたいです。
この使い分けについて、イメージが湧いていない状態であったため、どのような恩恵を使い分け、どのような恩恵を受けることができるかをインプットしたものをまとめました。
references
: データベースレベルでの外部キー制約relations
: アプリケーションレベルでのリレーション定義
1. 基本概念の違い
references
userId: uuid("user_id").notNull().references(() => usersTable.id)
- データベースの外部キー制約として機能
- 参照整合性を保証(親レコード削除時の動作なども制御可能)
- SQLの
FOREIGN KEY
制約としてマイグレーション時に反映される
relations
export const playersRelations = relations(playersTable, ({ one }) => ({
user: one(usersTable, {
fields: [playersTable.userId],
references: [usersTable.id],
}),
}));
- アプリケーションレベルのリレーション定義
- クエリビルダーでの簡易アクセス、型推論対応
- 一対一/一対多/多対多など、関係性を明示的に定義
2. 違いのまとめ
特性 | references | relations |
---|---|---|
スコープ | データベース | アプリケーション |
目的 | 整合性保証 | 型安全・クエリ効率 |
定義場所 | カラム定義内 | テーブル定義の外 |
効果 | 外部キー制約 | クエリの簡略化 |
型安全性 | ❌ | ✅ |
双方向定義 | ❌ | ✅(定義すれば) |
3. コード比較
relationsなし:カラム名変更時の全コード修正が必要
const result = await db
.select()
.from(playersTable)
.innerJoin(usersTable, eq(playersTable.userId, usersTable.id));
→ カラム名がuserReference
に変更されたら、全箇所で修正が必要
relationsあり:リレーション定義1箇所の修正だけでOK
// カラム名変更前
export const playersRelations = relations(playersTable, ({ one }) => ({
user: one(usersTable, {
fields: [playersTable.userId],
references: [usersTable.id],
}),
}));
// カラム名変更後(この1行だけ修正)
export const playersRelations = relations(playersTable, ({ one }) => ({
user: one(usersTable, {
fields: [playersTable.userReference], // ←変更箇所
references: [usersTable.id],
}),
}));
// クエリは変わらず動く
const result = await db.query.playersTable.findMany({
with: { user: true },
});
4. relationsを使わないデメリット
- クエリ記述が複雑になりやすい(JOINを手動で書く)
- 型安全性が得られない
- 関連データ取得時にN+1問題が起きやすい
- 関係性がコードベースで明示されず可読性が低下
- スキーマ変更時の修正箇所が多く、保守性が低下
5. 実装について
- 常に
references
とrelations
の両方を定義する - テーブル定義とリレーション定義を同じファイルに記述する
- 関係性(1:1, 1:N, N:M)を正しく設計する
6. 実装例
// テーブル定義
export const playersTable = pgTable("players", {
id: uuid("id").primaryKey().defaultRandom(),
userId: uuid("user_id").notNull().references(() => usersTable.id),
stageName: varchar("stage_name", { length: 255 }).notNull(),
});
// リレーション定義
export const playersRelations = relations(playersTable, ({ one }) => ({
user: one(usersTable, {
fields: [playersTable.userId],
references: [usersTable.id],
}),
}));
// Zodスキーマ
export const playerSelectSchema = createSelectSchema(playersTable);
export const playerInsertSchema = createInsertSchema(playersTable);
export const playerUpdateSchema = createUpdateSchema(playersTable);
7. 結論
Drizzle ORMでは、references
によるデータ整合性の確保と、relations
による型安全なアプリケーションロジックの意図で使い分けることで下記の恩恵を受けるこができる。
- クエリの簡略化と可読性向上
- 構造変更時の影響範囲の限定
- 型安全な開発体験
- 保守性・拡張性の向上