NestboltNestbolt

@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 ActorResolver to 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 -- AuditableMixin adds getAuditLogs() and getLatestAuditLog() convenience methods directly on your entity instances.
  • Event emission -- when @nestjs/event-emitter is installed, the module emits an audit.logged event 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 -- computeDiff can 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:

  1. Reads the entity's auditable metadata (type name, field filters, enabled actions).
  2. Computes a diff between the old and new values (for updates).
  3. Resolves the current actor via the configured ActorResolver or falls back to the defaultActor.
  4. Writes a new row to the audit_logs table within the same database transaction.
  5. Emits an audit.logged event if @nestjs/event-emitter is 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() and repository.remove() do trigger audit logging.
  • repository.update(), repository.delete(), and QueryBuilder.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:

ComponentPurpose
AuditLogModuleNestJS dynamic module providing forRoot and forRootAsync registration
AuditLogServiceInjectable service for manual logging, querying, and actor resolution
AuditSubscriberTypeORM entity subscriber that intercepts insert/update/delete events
@Auditable()Class decorator that marks an entity for automatic tracking
AuditableMixinMixin that adds audit query methods to entity instances
AuditLogEntityTypeORM entity representing the audit_logs table
ActorResolverInterface for resolving the current user/actor
computeDiffStandalone 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.