Implementing Soft Deletes

1. Adding Deleted Flag

ColumnTypeUse
isDeletedbooleanQuick filter
deletedAttimestampWhen deleted (null = active)
deletedByuserIdAudit trail

2. Adding Deleted Timestamp

Example: Soft-delete schema

type Post {
  id: ID!
  title: String!
  deletedAt: DateTime
  deletedBy: User
}

3. Filtering Deleted Records

Example: Default filter

posts: (_, args, { db }) =>
  db.post.findMany({
    where: { deletedAt: null, ...buildFilter(args.filter) }
  });

4. Implementing Restore Mutation

MutationAction
restorePost(id)Set deletedAt = null
deletePost(id)Set deletedAt = now
purgePost(id)Hard delete (admin only)

5. Implementing Permanent Delete

Example: Hard purge

purgePost: async (_, { id }, ctx) => {
  requireRole(ctx, "ADMIN");
  await db.post.delete({ where: { id } });
  return true;
}

6. Showing Deleted Records

PatternDetail
includeDeleted: Boolean = falseOpt-in flag (admin views)
trash queryDedicated deletedPosts field
Filter on deletedAtExpose deletedAt range filter

7. Using Deleted By Field

FieldReason
deletedByAudit who deleted
deletedReasonOptional context
deletedAtCompliance / retention

8. Cascading Soft Deletes

Example: Cascade transactionally

db.$transaction([
  db.post.update({ where: { id }, data: { deletedAt: now, deletedBy: userId } }),
  db.comment.updateMany({ where: { postId: id, deletedAt: null }, data: { deletedAt: now } })
]);

9. Archiving Deleted Data

StepDetail
Move to cold storageGlacier/archive table
Retentione.g. delete after 30 days in trash
ComplianceGDPR right-to-erasure honored

10. Implementing Trash/Recycle Bin

Example: Trash query

type Query {
  trash(first: Int = 20, after: String): PostConnection!
}
type Mutation {
  restorePost(id: ID!): Post!
  emptyTrash: Int!
}