YASD-TECH
YASD TECH
# Prisma

検索メソッドの使い分け(findUnique・findFirst・OrThrow)

投稿日:2026/7/26

更新日:2026/7/26

ttitleImage

Prisma の検索メソッド 技術メモ

Prisma で「1件取ってくる」メソッドの使い分けメモ。ActiveRecord と対応づけて整理する。

4つのメソッド

Prisma ActiveRecord 条件 見つからないとき 戻り値の型
findUnique find_by ユニーク制約のあるカラムのみ null T | null
findUniqueOrThrow find_by! / find ユニーク制約のあるカラムのみ 例外 T
findFirst find_by 任意 null T | null
findFirstOrThrow find_by! 任意 例外 T

OrThrow の有無が「nil を返すか例外を投げるか」、Unique / First の違いが「引ける条件の広さ」。

findUniquefindFirst の違い

findUniquewhere にユニーク制約のあるカラムしか渡せない。

// OK: 主キー
prisma.user.findUnique({ where: { userId } });

// OK: 複合ユニーク制約 @@unique([userId, organizationId])
prisma.membership.findUnique({
  where: { userId_organizationId: { userId, organizationId } },
});

// NG: name はユニークではない → 型エラー
prisma.user.findUnique({ where: { name: 'foo' } });

// 非ユニークな条件なら findFirst
prisma.user.findFirst({ where: { name: 'foo' } });

ActiveRecord の find_by は任意のカラムで引けるので、感覚としては findFirst の方が近い。findUnique は「主キーまたはユニークキーで引く」ことが型で強制されている分、ActiveRecord より狭い。

例外の対応

例外
ActiveRecord ActiveRecord::RecordNotFound
Prisma PrismaClientKnownRequestError(コード P2025

Rails が rescue_from ActiveRecord::RecordNotFound でコントローラ層で 404 にするのと同じ位置に、Node 側ならエラーハンドラ(tRPC の errorFormatter、Express の error middleware など)を置く。

方針の立て方

存在が前提のものは OrThrow を使うのを標準にしておくとよい。理由は2つある。

理由1: !(non-null assertion)が禁止されている

ESLint の @typescript-eslint/no-non-null-assertionerror にしていると、findUnique(...)! と書けない。findUnique を使うたびに null ガードを書くことになる。

// findUnique だとこう書く必要がある
const user = await prisma.user.findUnique({ where: { userId } });
if (user === null) {
  throw new Error('ユーザーが見つかりません');
}
user.organizationId;

// findUniqueOrThrow なら分岐が要らない
const user = await prisma.user.findUniqueOrThrow({ where: { userId } });
user.organizationId;

理由2: 分岐網羅 100% が必須

サービス層に branch coverage 100% を課している場合、if (user === null) を書けば、そのブランチを通すテストが要る。

「権限チェックを通っているので実際には起きないが、型の上ではありうる」ケースのためにテストを書くのは本質的ではない。最初から分岐を作らない方が素直。

/* istanbul ignore next */ で逃げる手もあるが、無視コメントが増えるとレビューで本当に見るべき箇所が埋もれる。

使い分けの判断基準

状況 使うもの
存在が前提。無いのはバグかデータ不整合 findUniqueOrThrow 権限チェックを通った後の対象取得
無いことが正常系にありうる findUnique + 分岐 重複チェック、招待済みかの確認
非ユニークな条件で1件欲しい findFirst 最新1件、条件に合う任意の1件

具体例

存在が前提のケース:

const user = await prisma.user.findUniqueOrThrow({
  where: { userId: input.userId },
});

input.userId は呼び出し前に権限チェックを通っているので、そこにレコードがいないのは異常事態。例外でよい。

無いことがありうるケース:

const membership = await prisma.membership.findUnique({ ... });
const isOrganizationAdmin = membership?.isOrganizationAdmin ?? false;

そのユーザーに Membership レコードが無いのが正常。ここで例外を投げてはいけない。

落とし穴

findFirst は順序が不定

orderBy を付けないと、どの1件が返るかは保証されない。「最新の1件」が欲しいなら明示する。

prisma.log.findFirst({
  where: { userId },
  orderBy: { createdAt: 'desc' },
});

OrThrow を try/catch で握らない

OrThrow を使ったうえで try { ... } catch { return null } と書くと、findUnique + 分岐と同じことを遠回りにやっているだけになる。しかも他の DB エラーまで飲み込む。

無いことがありうるなら最初から findUnique を使う。

トランザクション内で Promise.all しない

prisma.$transaction のコールバックに渡されるクライアントは単一コネクションに pin されているため、Promise.all で並列発行しても実際には逐次実行される。ドライバによっては deprecation warning が出る(pg は将来 fatal 化)。

// NG
const [a, b] = await Promise.all([
  tx.a.findMany(),
  tx.b.findMany(),
]);

// OK
const a = await tx.a.findMany();
const b = await tx.b.findMany();

ActiveRecord との対応まとめ

# Rails
user = User.find_by!(user_id: id)   # 無ければ RecordNotFound
user = User.find_by(user_id: id)    # 無ければ nil
user = User.where(name: 'foo').first # 任意条件で1件
// Prisma
const user = await prisma.user.findUniqueOrThrow({ where: { userId: id } });
const user = await prisma.user.findUnique({ where: { userId: id } });
const user = await prisma.user.findFirst({ where: { name: 'foo' } });

大きな違いは、Prisma は「ユニークキーで引く」ことを型で強制する点。ActiveRecord は実行時まで分からないが、Prisma は非ユニークなカラムを findUnique に渡すとコンパイルエラーになる。

Index

  • Prisma の検索メソッド 技術メモ
  • 4つのメソッド
  • findUnique と findFirst の違い
  • 例外の対応
  • 方針の立て方
  • 理由1: !(non-null assertion)が禁止されている
  • 理由2: 分岐網羅 100% が必須
  • 使い分けの判断基準
  • 具体例
  • 落とし穴
  • findFirst は順序が不定
  • OrThrow を try/catch で握らない
  • トランザクション内で Promise.all しない
  • ActiveRecord との対応まとめ