@studiosonrai/nestjs-audit-log
v1.1.3
Published
Audit logging module for NestJS + TypeORM: automatic CREATE/UPDATE capture via @Auditable entities, explicit DELETE, and queryable audit history
Readme
@studiosonrai/nestjs-audit-log
Audit logging for NestJS + TypeORM. Mark an entity @Auditable() and CREATE / UPDATE /
DELETE are recorded automatically by a TypeORM subscriber — no per-service code — for
repository.save(), repository.remove(), and repository.softRemove(). For legacy
repository.delete(id) paths there's a single-line AuditService.logEntity() escape
hatch. The acting user is captured from the request via CLS, so you never thread
userId through your services. The auditLog table is created at startup and read/written
via raw SQL through your existing DataSource, so no migration is required and you do
not need to add AuditLog to your entities array (Microsoft SQL Server).
Installation
npm install @studiosonrai/nestjs-audit-log
# peers (already present in most NestJS apps):
npm install @nestjs/typeorm @nestjs/swagger typeorm class-validator class-transformerSetup
import { AuditModule } from '@studiosonrai/nestjs-audit-log';
import { AuditLog } from '@studiosonrai/nestjs-audit-log';
@Module({
imports: [
AuditModule.forRootAsync({
imports: [TypeOrmModule.forFeature([User])], // for `resolveUser` if you want it
inject: [LoggerFactory, getRepositoryToken(User)],
useFactory: (
loggerFactory: LoggerFactory,
userRepo: Repository<User>,
) => ({
// Required if you want any package logs — otherwise the package is silent.
// Routes audit-log internal logs through your app's logger (winston,
// App Insights, whatever) with one context per internal class.
logger: (ctx) => loggerFactory.create(ctx),
// Optional: turn `changedBy` (a user id) into a name/email on read.
resolveUser: async (id) => {
const user = await userRepo.findOneBy({ id });
return user
? {
name: `${user.firstName} ${user.lastName ?? ''}`.trim(),
email: user.email,
}
: null;
},
// Optional: how to read the acting user id from the request.
// Defaults to `request.user?.id`.
getUserId: (req) => (req as RequestWithUser).user?.sub,
}),
}),
],
})
export class AppModule {}Register the AuditLog entity with your DataSource (e.g. autoLoadEntities: true, or add
AuditLog to your entities array / migrations) so the auditLog table exists.
The module mounts
nestjs-clsmiddleware globally to carry the request user into the TypeORM subscriber. If your app already configuresnestjs-cls, importAuditModuleonly once and avoid registering a secondClsModule.forRoot.
Automatic capture
import { Auditable } from '@studiosonrai/nestjs-audit-log';
@Auditable({
entityType: 'Contact', // default: the class name (You can change to your preferred name)
nameField: 'fullName', // property used as the human label
exclude: ['createdBy', 'updatedBy'], // omit from the diff (id/createdAt/updatedAt already excluded)
})
@Entity()
export class Contact { /* ... */ }entityType?— logical type stored in the log (default: class name)nameField?— property used as the human-readable nameexclude?: string[]— extra fields to omit from the diff (beyondid/createdAt/updatedAt)
CREATE and UPDATE are now logged automatically whenever the entity is inserted or saved
(loaded-then-save()), inside the same transaction as the operation.
DELETE and manual logging
repository.delete(id) does not fire TypeORM remove subscribers, so record deletes explicitly:
await this.auditService.logEntity(contact, AuditActionType.DELETE);
await this.contactRepository.delete(id);For full control use auditService.log({ entityType, entityId, action, changes, entityName }).
changedBy is filled from CLS when omitted.
Querying
AuditService exposes findAll(filter) (paginated), findByEntity, findByUser, findByAction.
Mount your own controller with your app's guards — the package ships no routes.
