В чём разница между findUnique, findFirst и findFirstOrThrow?
findUnique — только по уникальным ключам, возвращает T|null; findFirst — по любым полям с orderBy, возвращает T|null; findFirstOrThrow — то же, но бросает PrismaClientKnownRequestError (P2025) вместо null.
findUnique, findFirst и findFirstOrThrow
Три метода решают схожую задачу — получить одну запись — но отличаются гарантиями уникальности, поведением при отсутствии результата и генерируемым SQL.
findUnique
Принимает только уникальные поля: @id, поля с @unique или составные ключи @@unique. Генерирует WHERE ... LIMIT 1 с гарантией уникальности на уровне схемы. Если запись не найдена — возвращает null.
// Поиск по первичному ключу
const user = await prisma.user.findUnique({
where: { id: 42 },
});
// user: User | null
// По полю с @unique
const byEmail = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
});
// По составному @@unique
const membership = await prisma.membership.findUnique({
where: {
userId_orgId: { userId: 1, orgId: 5 },
},
});
Компилятор TypeScript запрещает передавать в where неуникальные поля — это отличает метод от остальных на уровне типов.
findFirst
Принимает произвольный where (любые поля), поддерживает orderBy, skip и cursor. Возвращает первую подходящую запись или null. Под капотом — SELECT ... LIMIT 1 без гарантии уникальности.
// Последний активный пост пользователя
const latestPost = await prisma.post.findFirst({
where: {
authorId: 42,
published: true,
},
orderBy: { createdAt: 'desc' },
});
// latestPost: Post | null
// С пропуском нескольких записей
const secondPost = await prisma.post.findFirst({
where: { authorId: 42 },
orderBy: { createdAt: 'asc' },
skip: 1,
});
findFirstOrThrow (и findUniqueOrThrow)
Идентичен findFirst по сигнатуре, но при отсутствии записи выбрасывает PrismaClientKnownRequestError с кодом P2025 вместо возврата null. Тип возврата — T (не T | null), что упрощает дальнейший код.
import { Prisma } from '@prisma/client';
try {
const post = await prisma.post.findFirstOrThrow({
where: { slug: 'my-post', published: true },
orderBy: { createdAt: 'desc' },
});
// post: Post — гарантированно не null
console.log(post.title);
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2025'
) {
// 404 Not Found
}
throw e;
}
Аналогично работает findUniqueOrThrow — как findUnique, но бросает исключение.
Сравнительная таблица
- findUnique — только уникальные ключи, возвращает
T | null, ошибка компиляции при неуникальном поле. - findFirst — любой where + orderBy, возвращает
T | null. - findFirstOrThrow — любой where + orderBy, возвращает
T, бросает P2025 при отсутствии.
Подводные камни
- findFirst без orderBy недетерминирован — при нескольких подходящих записях база вернёт любую; всегда указывайте
orderBy, если порядок важен. - findUnique не принимает orderBy — если нужна сортировка, используйте findFirst; попытка добавить orderBy в findUnique даст ошибку типов.
- P2025 может маскировать другие ошибки — перехватывайте только нужный код, иначе скроете, например, ошибки соединения с БД.
- findFirst медленнее на больших таблицах без индекса — в отличие от findUnique, база не знает о гарантии уникальности и может не оптимизировать запрос; добавляйте индексы на поля в where.
- findUniqueOrThrow vs findFirstOrThrow — путаница между ними приводит к тому, что неуникальные поля попадают в findUnique и ломают компиляцию.
- Тип null в TypeScript — игнорирование
| nullу findFirst без проверки ведёт к runtime-ошибкам; используйте optional chaining или явную проверку.
Common mistakes
- Путает Prisma Client API с гарантиями базы данных: индексы, блокировки и isolation level не создаются магически.
- Не объясняет, где в lifecycle находится findUnique, findFirst и findFirstOrThrow.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить findUnique, findFirst и findFirstOrThrow на примере кода.
- Называет ключевые API: findUnique(), findFirst(), findFirstOrThrow().
- Отделяет ORM/query builder поведение от реального поведения СУБД.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.