投稿日:2026/7/26
更新日:2026/7/26

Prisma で「1件取ってくる」メソッドの使い分けメモ。ActiveRecord と対応づけて整理する。
| 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 の違いが「引ける条件の広さ」。
findUnique と findFirst の違いfindUnique は where にユニーク制約のあるカラムしか渡せない。
// 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つある。
!(non-null assertion)が禁止されているESLint の @typescript-eslint/no-non-null-assertion を error にしていると、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;
サービス層に 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();
# 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 に渡すとコンパイルエラーになる。