Implementing Soft Delete Pattern
1. Adding deletedAt Field
deletedAt DateTime?
@@index([deletedAt])
| Aspect | Detail |
| Nullable | Null = active; date = deleted |
| Index | Improves filtered scans |
2. Using Middleware for Soft Delete
| Approach | Detail |
Legacy $use | Intercept delete/findMany |
| Extension | Use $extends.query hooks |
3. Filtering Deleted Records
const active = await prisma.user.findMany({ where: { deletedAt: null } });
| Strategy | Detail |
| Explicit filter | Add to every read |
| Repository wrapper | Encapsulate filter in DAO |
| Extension | Automatic injection |
4. Implementing Restore Functionality
await prisma.user.update({ where: { id }, data: { deletedAt: null } });
| Step | Detail |
| Reset flag | Set deletedAt to null |
| Audit | Log restoration event |
| When | Detail |
| GDPR erasure | Permanently remove |
| Cleanup jobs | After retention window |
6. Using Global Soft Delete Scope
const xprisma = prisma.$extends({
query: {
$allModels: {
async findMany({ args, query }) {
args.where = { ...args.where, deletedAt: null };
return query(args);
}
}
}
});
| Aspect | Detail |
| Scope | Apply to all or specific models |
| Bypass | Use raw client for admin tools |
7. Handling Relation Cascades
| Pattern | Detail |
| Cascade soft-delete | Update children's deletedAt |
| Detach | Keep children, null FK |
8. Archiving Deleted Records
| Strategy | Detail |
| Archive table | Move to separate model |
| Partitioning | PG declarative partitions |
| Cold storage | Export to S3/Glacier |
9. Implementing Paranoid Mode
| Concept | Detail |
| Default safe reads | Always exclude deleted |
| Explicit opt-in | Need flag to include deleted |
10. Querying Including Deleted Records
const all = await prisma.user.findMany(); // bypass extension
const onlyDeleted = await prisma.user.findMany({ where: { deletedAt: { not: null } } });
| Need | Approach |
| All rows | Use base client without extension |
| Only deleted | Filter deletedAt: { not: null } |