Create Branch

メモ:Drizzle ORMのreferencesとrelationsの違い

Published on 2025年5月7日

database

はじめに

TypeScriptベースのORMであるDrizzle ORMでは、テーブル間の関連付け方法としてreferencesrelationsの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を使わないデメリット

  1. クエリ記述が複雑になりやすい(JOINを手動で書く)
  2. 型安全性が得られない
  3. 関連データ取得時にN+1問題が起きやすい
  4. 関係性がコードベースで明示されず可読性が低下
  5. スキーマ変更時の修正箇所が多く、保守性が低下

5. 実装について

  1. 常にreferencesrelationsの両方を定義する
  2. テーブル定義とリレーション定義を同じファイルに記述する
  3. 関係性(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による型安全なアプリケーションロジックの意図で使い分けることで下記の恩恵を受けるこができる。

  • クエリの簡略化と可読性向上
  • 構造変更時の影響範囲の限定
  • 型安全な開発体験
  • 保守性・拡張性の向上
hirotobeat