mediumFull Stack EngineerSaaS
How would you implement soft delete and data filtering with Prisma middleware?
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At a SaaS company interview:
> "Users want to 'delete' their job applications but we need to keep the data for analytics. We also need to filter out deleted records from all queries automatically. How would you implement this with Prisma?"
> "Users want to 'delete' their job applications but we need to keep the data for analytics. We also need to filter out deleted records from all queries automatically. How would you implement this with Prisma?"
Suggested Solution
Soft Delete with Prisma
Approach 1: Prisma Client Extensions (Recommended — Prisma 4.16+)
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient().$extends({
name: "softDelete",
model: {
jobApplication: {
async findMany(args) {
return prisma.jobApplication.findMany({
...args,
where: { deletedAt: null, ...args?.where },
});
},
async delete(args) {
return prisma.jobApplication.update({
where: args.where,
data: { deletedAt: new Date() },
});
},
async deleteMany(args) {
return prisma.jobApplication.updateMany({
...args,
data: { deletedAt: new Date() },
});
},
},
},
});
Approach 2: Base Schema Pattern
model JobApplication {
id String @id @default(auto()) @map("_id") @db.ObjectId
company String
position String
status String @default("APPLIED")
deletedAt DateTime? // NULL = active, has value = soft deleted
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId, deletedAt]) // Efficient filtering
}
Usage in API Routes
// GET /api/applications — auto-filters soft-deleted
const apps = await prisma.jobApplication.findMany({
where: { userId: session.userId },
include: { interviews: true },
});
// Client extension automatically adds deletedAt: null filter
// DELETE /api/applications/[id] — soft delete
await prisma.jobApplication.delete({
where: { id: applicationId },
});
// Client extension converts this to UPDATE deletedAt = now()
// Analytics — bypass soft delete filter
const allApps = await prisma.jobApplication.findMany({
where: { deletedAt: { not: null } }, // Explicitly query deleted
});
Restoring Soft-Deleted Records
await prisma.jobApplication.update({
where: { id: applicationId },
data: { deletedAt: null },
});
Production Considerations
deletedAt — @@unique([company, position, userId, deletedAt])deletedAt to composite indexescount({ where: { deletedAt: null } })