Soft Delete
Soft delete allows you to mark records as deleted without actually removing them from the database. This is useful for:
- Audit trails
- Data recovery
- Maintaining referential integrity
- Compliance requirements
Overview
Section intitulée « Overview »When soft delete is enabled:
- Records are marked as deleted (typically with a
deletedAttimestamp) - Deleted records are excluded from queries by default
- You can query deleted records using the
withDeletedparameter - The
deleteOneanddeleteManymutations mark records as deleted instead of removing them
Enabling Soft Delete
Section intitulée « Enabling Soft Delete »Enable soft delete in your resolver options:
import { buildTypeDefsAndResolvers } from '@lib/type-graphql-query';import { Product } from './dto/product.type';import { product as productTable } from '@lib/db';
const { resolvers, typeDefs } = await buildTypeDefsAndResolvers({ resolvers: [ { DTOClass: Product, EntityClass: productTable, delete: { useSoftDelete: true, }, }, ],});Database Schema
Section intitulée « Database Schema »Your entity should have a deletedAt field (or similar) to track deletion:
// Example with Drizzleexport const product = pgTable('product', { id: uuid('id').primaryKey(), name: varchar('name', { length: 255 }), price: decimal('price', { precision: 10, scale: 2 }), deletedAt: timestamp('deleted_at', { withTimezone: true }), // ... other fields});Querying Records
Section intitulée « Querying Records »By default, soft-deleted records are excluded from queries:
GraphQL:
{ products { edges { node { id name price } } }}This query will only return records where deletedAt IS NULL.
Querying Deleted Records
Section intitulée « Querying Deleted Records »Use the withDeleted parameter to include deleted records:
GraphQL:
{ products(withDeleted: true) { edges { node { id name price } } }}Querying Only Deleted Records
Section intitulée « Querying Only Deleted Records »You can filter to show only deleted records:
GraphQL:
{ products(filter: { deletedAt: { isNot: null } }, withDeleted: true) { edges { node { id name price } } }}Deleting Records
Section intitulée « Deleting Records »When soft delete is enabled, the deleteOne and deleteMany mutations mark records as deleted:
GraphQL:
mutation { deleteOneProduct(input: { id: "1" }) { id name price }}The record is not removed from the database; instead, its deletedAt field is set to the current timestamp.
Automatic Filtering with ContextFilter
Section intitulée « Automatic Filtering with ContextFilter »You can use @ContextFilter() to automatically exclude soft-deleted records:
import { ObjectType, Field } from '@lib/type-graphql';import { ContextFilter, OperationContext } from '@lib/type-graphql-query';
@ObjectType()@ContextFilter<Product>((ctx: OperationContext) => { // Automatically exclude soft-deleted items for read operations // The withDeleted flag in the query will override this if (ctx.operationType === 'READ') { return { deletedAt: { is: null }, }; }
// For mutations, allow access to soft-deleted items return undefined;})export class Product { @Field() id!: string;
@Field() name!: string;
@Field({ nullable: true }) deletedAt?: Date;}Restoring Deleted Records
Section intitulée « Restoring Deleted Records »To restore a soft-deleted record, you can update it to set deletedAt to null:
GraphQL:
mutation { updateOneProduct(input: { id: "1", update: { deletedAt: null } }) { id name deletedAt }}Hard Delete
Section intitulée « Hard Delete »If you need to actually delete records from the database, you can:
- Disable soft delete for specific resolvers
- Use a custom mutation that performs hard delete
- Access the QueryService directly to perform hard delete
Best Practices
Section intitulée « Best Practices »- Use soft delete by default - It provides better data safety and audit trails
- Index the deletedAt field - This improves query performance when filtering deleted records
- Consider cleanup jobs - Periodically archive or permanently delete old soft-deleted records
- Document deletion policies - Make it clear when records are soft-deleted vs hard-deleted
- Use withDeleted carefully - Only query deleted records when necessary for admin or audit purposes
Next Steps
Section intitulée « Next Steps »- ContextFilter - Automatic filtering including soft delete
- Mutations - Delete mutations
- Queries - Query operations with withDeleted