Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】【Prisma】findFirst をユニーク検索に使うと危ない

2025年09月08日に「Zenn」が公開したITニュース「【Prisma】findFirst をユニーク検索に使うと危ない」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

データベース操作ライブラリPrismaで、IDなど一意な値でデータを1件検索する際は`findUnique`が推奨される。`findFirst`も同様に動作するが、型チェックが緩く、予期せぬ不具合に繋がる危険性があるため、意図に合ったメソッドを正しく使い分けることが重要だ。(118文字)

ITニュース解説

Webアプリケーション開発において、データベースから特定のデータを1件だけ取り出す操作は、ユーザー情報の表示や更新など、様々な場面で頻繁に行われる。例えば、ユーザーIDを指定して、そのユーザーの情報をデータベースから取得するケースがこれにあたる。このようなデータベース操作を、プログラミング言語のコードとしてより直感的に、そして安全に記述できるようにするための技術がORM(Object-Relational Mapping)である。近年、TypeScriptやNode.jsを用いた開発で広く採用されているORMの一つにPrismaがある。

Prismaを使って、データベースから特定の1件のデータを取得する際、主に二つのメソッドが用意されている。それはfindUniquefindFirstだ。データベースのテーブルには、各データを一意に識別するための「主キー(Primary Key)」や、値の重複を許さない「ユニーク制約(Unique Constraint)」が設定されたカラムが存在する。例えば、userテーブルのidカラムやemailカラムがこれに該当する。このようなユニークな値を持つカラムを条件にデータを1件だけ検索する場合、findUniquefindFirstは、一見すると全く同じように機能する。

具体的には、idが特定の値であるユーザーを取得するコードは、それぞれ次のように書くことができる。findUniqueを使った場合はawait prisma.user.findUnique({ where: { id: userId } })となり、findFirstを使った場合はawait prisma.user.findFirst({ where: { id: userId } })となる。どちらのコードを実行しても、結果として同じユーザーのデータ、あるいはデータが存在しない場合はnullが返される。このため、どちらのメソッドを使っても違いはないように思えるかもしれない。しかし、ユニークな値を条件とする検索においてfindFirstを使用することには、将来的に深刻なバグを引き起こす可能性のある、重大な危険性が潜んでいる。

この危険性の根源は、それぞれのメソッドが検索条件として受け入れる「型」の定義の違いにある。findUniqueメソッドは、その名の通り、ユニークな値での検索専用に設計されている。そのため、where句に指定できるのは、Prismaがスキーマ定義から認識している主キーやユニーク制約を持つカラムのみに限定される。これは、開発者が「必ず1件しかヒットしない、あるいは全くヒットしない」という検索を意図していることを、コードレベルで保証するための強力な制約である。もし誤ってユニークではないカラムを検索条件に指定しようとすると、プログラムを実行する前のコーディング段階でTypeScriptが型エラーを検知し、間違いを知らせてくれる。

一方で、findFirstメソッドはより汎用的な目的のために存在する。これは「指定した条件に合致するレコードが複数ある場合に、その中から最初に見つかった1件を取得する」ためのメソッドだ。そのため、where句にはどのようなカラムでも、あるいは複数のカラムの組み合わせでも、自由に指定することが許されている。この柔軟性が、ユニーク検索の文脈で使われると裏目に出る。

具体的なシナリオを考えてみよう。ある開発者が、ユーザーIDを元にユーザー情報を取得する処理をfindFirstを使って実装したとする。この時点では、where: { id: userId }という条件であるため、正しく機能する。しかし後日、別の開発者がこの処理に手を入れることになり、「ユーザーIDに加えて、特定のグループに所属していることも条件に加えたい」と考え、where: { id: userId, groupId: 1 }のように検索条件を追加したとする。

もし元の実装がfindUniqueを使っていれば、ユニークキーではないgroupIdwhere句に追加した時点で型エラーが発生する。これにより、開発者は「この処理は本来ユニークキーでのみ検索されるべきだ」と気づき、意図しない変更を防ぐことができる。しかし、findFirstは複数の条件を許容するため、この変更はエラーにならず、プログラムは問題なく動作してしまう。その結果、本来は「IDがuserIdであるユーザー」を対象とすべき処理が、「IDがuserIdで、かつグループIDが1であるユーザー」という、より限定された意図しない条件の処理へと静かに変貌してしまう。これにより、対象ユーザーがグループID1に所属していなかった場合にデータが取得できなくなり、予期せぬ不具合を引き起こす原因となる。

この問題は、データの読み取りだけでなく、更新や削除といったよりクリティカルな処理において、さらに深刻な事態を招きかねない。意図したレコードが更新・削除されないといった不具合に繋がる可能性があるからだ。そして最も厄介なのは、この種のバグはプログラムのコンパイル時には一切検知されず、実行時の特定のデータ条件でのみ顕在化するため、発見が遅れがちになるという点である。

したがって、データベースから主キーやユニークキーといった一意な識別子を使って特定の1件のデータを取得するという意図がある場合は、必ずfindUniqueを使用するべきである。findUniqueが課す制約は、コードの意図を明確に表現し、将来にわたるコードの変更に対して安全性を担保するための重要な仕組みだ。findFirstは、検索結果が複数存在しうる条件の中から最初の1件を取得したい、という明確なユースケースでのみ使用するべきであり、ユニーク検索の安易な代替として利用してはならない。適切な道具を適切な目的で使い分けることが、堅牢でメンテナンス性の高いシステムを構築するための基本原則なのである。

関連コンテンツ