@nestbolt/audit-log
Introduction
Automatic entity change tracking and audit logging for NestJS with TypeORM.
@nestbolt/audit-log is a NestJS module that automatically tracks insert, update, and delete operations on your TypeORM entities. It captures old and new field values as diffs, resolves the actor (user) who made the change, and stores a complete audit trail in an audit_logs database table. The module integrates directly with TypeORM's subscriber system, so tracking changes requires nothing more than decorating your entities.
Key Features
- Automatic change tracking -- insert, update, and delete operations are captured via TypeORM entity subscribers. No manual instrumentation needed for standard ORM operations.
- Field-level diffs -- updates record precisely which fields changed, storing both the old and new values as JSON.
- Actor resolution -- plug in your own
ActorResolverto automatically tag every audit log entry with the user or system that made the change. - Configurable field filtering -- exclude sensitive fields like passwords and tokens globally or per entity, or whitelist only the fields you care about.
- Per-entity control -- the
@Auditable()decorator lets you configure tracked fields, override the entity type name, and limit which actions (created, updated, deleted) are logged. - Manual logging -- use
AuditLogService.log()for operations that bypass the ORM, such as bulk SQL updates, external API calls, or business events. - Query API -- retrieve audit logs by entity, by actor, or fetch the latest entry with built-in filtering by action, date range, and pagination.
- Entity mixin --
AuditableMixinaddsgetAuditLogs()andgetLatestAuditLog()convenience methods directly on your entity instances. - Event emission -- when
@nestjs/event-emitteris installed, the module emits anaudit.loggedevent after every audit log is written, enabling downstream processing such as notifications, analytics, or replication. - Global module -- register once in your root module and the service is available throughout your entire application.
- Standalone utilities --
computeDiffcan be imported and used independently without registering the module.
Quick Example
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
import { Auditable, AuditableMixin } from "@nestbolt/audit-log";
@Entity("users")
@Auditable({ except: ["password"] })
export class User extends AuditableMixin(BaseEntity) {
@PrimaryGeneratedColumn("uuid") id!: string;
@Column() name!: string;
@Column() email!: string;
@Column() password!: string;
}With this setup, every .save() and .remove() call on a User entity will produce an audit log entry in the audit_logs table, recording the action, the changed fields (excluding password), and the resolved actor.
// Insert -- creates audit log with action "created"
const user = repo.create({ name: "Alice", email: "alice@example.com", password: "hashed" });
await repo.save(user);
// Update -- creates audit log with action "updated" and field diff
user.name = "Bob";
await repo.save(user);
// Recorded: oldValues: { name: "Alice" }, newValues: { name: "Bob" }
// Delete -- creates audit log with action "deleted"
await repo.remove(user);How It Works
The module registers a TypeORM EntitySubscriber that listens for afterInsert, beforeUpdate/afterUpdate, and afterRemove lifecycle events. When an event fires on an entity decorated with @Auditable(), the subscriber:
- Reads the entity's auditable metadata (type name, field filters, enabled actions).
- Computes a diff between the old and new values (for updates).
- Resolves the current actor via the configured
ActorResolveror falls back to thedefaultActor. - Writes a new row to the
audit_logstable within the same database transaction. - Emits an
audit.loggedevent if@nestjs/event-emitteris available.
Because the audit log is written inside the same transaction as the entity operation, audit entries are only persisted when the operation itself succeeds.
Important Limitations
Only TypeORM operations that trigger entity lifecycle events produce automatic audit logs. This means:
repository.save()andrepository.remove()do trigger audit logging.repository.update(),repository.delete(), andQueryBuilder.update()/QueryBuilder.delete()do not trigger audit logging because they bypass entity lifecycle events.
For operations that bypass the subscriber, use the AuditLogService.log() method to create audit entries manually. See the Manual Logging page for details.
Architecture Overview
The module consists of the following components:
| Component | Purpose |
|---|---|
AuditLogModule | NestJS dynamic module providing forRoot and forRootAsync registration |
AuditLogService | Injectable service for manual logging, querying, and actor resolution |
AuditSubscriber | TypeORM entity subscriber that intercepts insert/update/delete events |
@Auditable() | Class decorator that marks an entity for automatic tracking |
AuditableMixin | Mixin that adds audit query methods to entity instances |
AuditLogEntity | TypeORM entity representing the audit_logs table |
ActorResolver | Interface for resolving the current user/actor |
computeDiff | Standalone utility for computing old/new value diffs |
Next Steps
- Installation -- install the package and its peer dependencies.
- Quick Start -- get audit logging running in under five minutes.
- Configuration -- learn about all available module options.