@nestbolt/soft-delete
v0.1.0
Published
Soft delete for NestJS with TypeORM — mark entities as deleted instead of removing, with restore, force delete, and query helpers.
Downloads
153
Maintainers
Readme
This package provides soft delete functionality for NestJS with TypeORM that lets you mark entities as deleted instead of permanently removing them, with restore, force delete, and query helpers.
Once installed, using it is as simple as:
@SoftDeletable()
@Entity()
class Post extends SoftDeletableMixin(BaseEntity) {
@Column({ name: 'deleted_at', nullable: true }) deletedAt: Date | null;
}
await post.softDelete(); // Marks as deleted
await post.restore(); // Restores
await post.forceDelete(); // Permanently removesTable of Contents
- Installation
- Quick Start
- Module Configuration
- Using the Decorator
- Using the Mixin
- Using the Service Directly
- Query Helpers
- Custom Column Name
- Events
- Configuration Options
- Testing
- Changelog
- Contributing
- Security
- Credits
- License
Installation
Install the package via npm:
npm install @nestbolt/soft-deleteOr via yarn:
yarn add @nestbolt/soft-deleteOr via pnpm:
pnpm add @nestbolt/soft-deletePeer Dependencies
This package requires the following peer dependencies:
@nestjs/common ^10.0.0 || ^11.0.0
@nestjs/core ^10.0.0 || ^11.0.0
typeorm ^0.3.0
reflect-metadata ^0.1.13 || ^0.2.0Optional:
@nestjs/event-emitter ^2.0.0 || ^3.0.0Quick Start
- Register the module in your
AppModule:
import { SoftDeleteModule } from '@nestbolt/soft-delete';
@Module({
imports: [
TypeOrmModule.forRoot({ /* ... */ }),
SoftDeleteModule.forRoot(),
],
})
export class AppModule {}- Add the decorator and mixin to your entity:
import { SoftDeletable, SoftDeletableMixin } from '@nestbolt/soft-delete';
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';
@SoftDeletable()
@Entity('posts')
export class Post extends SoftDeletableMixin(BaseEntity) {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
title: string;
@Column({ name: 'deleted_at', type: 'datetime', nullable: true, default: null })
deletedAt: Date | null;
}- Use soft delete in your service:
// Via mixin methods
await post.softDelete();
await post.restore();
console.log(post.isDeleted()); // true/false
// Via service
await softDeleteService.softDelete(Post, postId);
await softDeleteService.restore(Post, postId);
await softDeleteService.forceDelete(Post, postId);Module Configuration
Static Configuration (forRoot)
SoftDeleteModule.forRoot({
columnName: 'deleted_at', // Default column name
})Async Configuration (forRootAsync)
SoftDeleteModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
columnName: config.get('SOFT_DELETE_COLUMN', 'deleted_at'),
}),
})The module is registered as global — SoftDeleteService is available everywhere without re-importing.
Using the Decorator
The @SoftDeletable() class decorator marks an entity as soft-deletable:
@SoftDeletable() // Uses default column 'deleted_at'
@SoftDeletable({ columnName: 'removed_at' }) // Custom column nameUsing the Mixin
The SoftDeletableMixin() adds instance methods to your entity:
| Method | Returns | Description |
|--------|---------|-------------|
| softDelete() | Promise<void> | Soft-delete this entity |
| restore() | Promise<void> | Restore this entity |
| forceDelete() | Promise<void> | Permanently delete from DB |
| isDeleted() | boolean | Check if soft-deleted |
| isTrashed() | boolean | Alias for isDeleted() |
| getDeletedAt() | Date \| null | Get deletion timestamp |
Using the Service Directly
Inject SoftDeleteService for programmatic control:
| Method | Returns | Description |
|--------|---------|-------------|
| softDelete<T>(Entity, id) | Promise<void> | Set deletedAt to now |
| restore<T>(Entity, id) | Promise<void> | Set deletedAt to null |
| forceDelete<T>(Entity, id) | Promise<void> | Permanently DELETE |
| withTrashed<T>(Entity, alias?) | SelectQueryBuilder<T> | Query including deleted |
| onlyTrashed<T>(Entity, alias?) | SelectQueryBuilder<T> | Query only deleted |
| isSoftDeletable(Entity) | boolean | Check if decorated |
| getColumnName(Entity?) | string | Resolve column name |
Query Helpers
// Get all posts including soft-deleted
const all = await softDeleteService.withTrashed(Post).getMany();
// Get only soft-deleted posts
const trashed = await softDeleteService.onlyTrashed(Post).getMany();
// Add conditions to trashed query
const old = await softDeleteService
.onlyTrashed(Post, 'post')
.andWhere('post.createdAt < :date', { date: someDate })
.getMany();Custom Column Name
Override the column name per entity or globally:
// Per entity
@SoftDeletable({ columnName: 'removed_at' })
// Globally
SoftDeleteModule.forRoot({ columnName: 'removed_at' })Entity-level options take priority over module-level options.
Events
When @nestjs/event-emitter is installed, the following events are emitted:
| Event | Payload |
|-------|---------|
| soft-delete.deleted | { entityType, entityId } |
| soft-delete.restored | { entityType, entityId } |
| soft-delete.force-deleted | { entityType, entityId } |
Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| columnName | string | 'deleted_at' | Default column name for soft-delete timestamp |
Testing
npm testRun tests in watch mode:
npm run test:watchGenerate coverage report:
npm run test:covChangelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please report them via GitHub Issues with the security label instead of using the public issue tracker.
Credits
- Inspired by spatie/laravel-model-cleanup and Laravel's built-in
SoftDeletestrait
License
The MIT License (MIT). Please see License File for more information.
