PrismaSeniorTechnical
Что такое middleware Prisma (устаревший) и расширения Prisma Client (Prisma Client extensions)? Что такое $extends?
$use (deprecated) перехватывал все запросы через middleware-цепочку. $extends (актуальный, Prisma 4.16+) создаёт новый типобезопасный клиент с расширениями query, result, model и client — поддерживает вычисляемые поля, кастомные методы и перехват операций.
Prisma Middleware ($use) — устаревший подход
До Prisma 4.16 для перехвата запросов использовался prisma.$use() — middleware-функция, получающая params (модель, action, аргументы) и next для вызова следующего обработчика. Начиная с Prisma 5 этот API помечен как deprecated.
// УСТАРЕВШИЙ подход — $use (Prisma 4.x и ниже)
const prisma = new PrismaClient();
prisma.$use(async (params, next) => {
const before = Date.now();
const result = await next(params);
const after = Date.now();
console.log(`${params.model}.${params.action} took ${after - before}ms`);
return result;
});
// Soft delete через middleware
prisma.$use(async (params, next) => {
if (params.model === 'Post' && params.action === 'delete') {
params.action = 'update';
params.args.data = { deletedAt: new Date() };
}
return next(params);
});
Prisma Client Extensions ($extends) — актуальный подход
$extends — это новый API (GA с Prisma 4.16), который заменяет middleware. Он создаёт новый экземпляр клиента с расширенным поведением и полностью типобезопасен. Поддерживает четыре компонента расширений: model, client, query, result.
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient().$extends({
// query — перехват запросов (аналог middleware)
query: {
post: {
async delete({ args, query }) {
// Soft delete вместо физического удаления
return prisma.post.update({
where: args.where,
data: { deletedAt: new Date() },
});
},
},
},
// result — вычисляемые поля на уровне типов
result: {
user: {
fullName: {
needs: { firstName: true, lastName: true },
compute(user) {
return `${user.firstName} ${user.lastName}`;
},
},
},
},
// model — дополнительные методы к моделям
model: {
user: {
async findByEmail(email: string) {
return prisma.user.findUnique({ where: { email } });
},
},
},
// client — методы на уровне самого клиента
client: {
async healthCheck() {
await prisma.$queryRaw`SELECT 1`;
return true;
},
},
});
// Использование вычисляемого поля — fullName типизирован
const user = await prisma.user.findUnique({ where: { id: 1 } });
console.log(user?.fullName); // string | undefined — всё типобезопасно
// Вызов кастомного метода модели
const found = await prisma.user.findByEmail('alice@example.com');
Композиция расширений
// Расширения можно комбинировать цепочкой
const extendedPrisma = new PrismaClient()
.$extends(loggingExtension)
.$extends(softDeleteExtension)
.$extends(tenantExtension);
// Или выносить в отдельные модули и реиспользовать
const loggingExtension = Prisma.defineExtension({
query: {
$allModels: {
async $allOperations({ operation, model, args, query }) {
const start = performance.now();
const result = await query(args);
console.log(`${model}.${operation}: ${performance.now() - start}ms`);
return result;
},
},
},
});
Подводные камни
$extendsсоздаёт новый экземпляр клиента — переменнаяprismaпосле.$extends()это не тот же объект, нужно переприсваивать результат.- Внутри
queryрасширения нельзя использовать исходныйprismaдля рекурсивных вызовов — нужно либо использоватьquery(args), либо аккуратно управлять ссылками. - Расширения типа
resultдобавляют поля только в TypeScript-типы, не в БД — данные вычисляются в рантайме на Node.js стороне. - Middleware (
$use) и$extendsнельзя смешивать в одном клиенте — при миграции на расширения нужно полностью переписать middleware. - Расширения
modelне видны в транзакциях ($transaction) — кастомные методы нужно вызывать до входа в транзакцию или передавать клиент отдельно. Prisma.defineExtensionнужен для создания реиспользуемых расширений — иначе TypeScript не может правильно вывести тип расширенного клиента в другом файле.- Расширения не заменяют database-level триггеры: если нужна атомарность soft delete, лучше использовать базу данных или явные транзакции.
Common mistakes
- Путает Prisma Client API с гарантиями базы данных: индексы, блокировки и isolation level не создаются магически.
- Не объясняет, где в lifecycle находится устаревший middleware и Client extensions.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить устаревший middleware и Client extensions на примере кода.
- Называет ключевые API: $extends(), $use.
- Отделяет ORM/query builder поведение от реального поведения СУБД.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.