@nestbolt/audit-log
Quick Start
Get audit logging running in your NestJS application in under five minutes.
This guide walks you through setting up @nestbolt/audit-log from scratch: registering the module, marking an entity as auditable, and verifying that changes are tracked.
1. Register the Module
Import AuditLogModule in your root module and call forRoot():
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { AuditLogModule } from "@nestbolt/audit-log";
import { User } from "./entities/user.entity";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
database: "myapp",
username: "postgres",
password: "postgres",
autoLoadEntities: true,
synchronize: true,
}),
TypeOrmModule.forFeature([User]),
AuditLogModule.forRoot({
globalExcludedFields: ["password", "passwordHash", "token"],
}),
],
})
export class AppModule {}The module is registered globally. You do not need to import it again in feature modules.
2. Create an Auditable Entity
Decorate your entity with @Auditable() and extend AuditableMixin(BaseEntity):
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
import { Auditable, AuditableMixin } from "@nestbolt/audit-log";
@Entity("users")
@Auditable({ except: ["passwordHash"] })
export class User extends AuditableMixin(BaseEntity) {
@PrimaryGeneratedColumn("uuid")
id!: string;
@Column()
name!: string;
@Column()
email!: string;
@Column()
role!: string;
@Column()
passwordHash!: string;
}The @Auditable() decorator marks this entity for automatic change tracking. The except option ensures the passwordHash field is never recorded in audit logs. The AuditableMixin adds convenience methods like getAuditLogs() directly on the entity instance.
3. Perform Operations and See Audit Logs
Now, every .save() and .remove() call on the User entity will create an audit log entry.
Create a User
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { AuditLogService } from "@nestbolt/audit-log";
import { User } from "./entities/user.entity";
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepo: Repository<User>,
private readonly auditLogService: AuditLogService,
) {}
async createUser(name: string, email: string): Promise<User> {
const user = this.userRepo.create({
name,
email,
role: "member",
passwordHash: "hashed_value",
});
return this.userRepo.save(user);
// Audit log created automatically:
// action: "created"
// entityType: "User"
// entityId: "<generated-uuid>"
// newValues: { name: "Alice", email: "alice@example.com", role: "member" }
// (passwordHash is excluded)
}
async updateUser(userId: string, name: string): Promise<User> {
const user = await this.userRepo.findOneByOrFail({ id: userId });
user.name = name;
return this.userRepo.save(user);
// Audit log created automatically:
// action: "updated"
// oldValues: { name: "Alice" }
// newValues: { name: "Bob" }
}
async deleteUser(userId: string): Promise<void> {
const user = await this.userRepo.findOneByOrFail({ id: userId });
await this.userRepo.remove(user);
// Audit log created automatically:
// action: "deleted"
// oldValues: { name: "Bob", email: "alice@example.com", role: "member" }
}
async getUserHistory(userId: string) {
return this.auditLogService.getAuditLogs("User", userId);
}
}Query Audit Logs via the Service
// Get all audit logs for a specific user
const logs = await this.auditLogService.getAuditLogs("User", userId);
// Get only update logs from the last 30 days
const updates = await this.auditLogService.getAuditLogs("User", userId, {
action: "updated",
from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
limit: 50,
});
// Get the most recent audit log entry
const latest = await this.auditLogService.getLatestAuditLog("User", userId);Query Audit Logs via the Entity
Because User extends AuditableMixin, you can query logs directly from an entity instance:
const user = await this.userRepo.findOneByOrFail({ id: userId });
// Get all audit logs for this user
const logs = await user.getAuditLogs();
// Get the latest audit log
const latest = await user.getLatestAuditLog();
// Get only the last 5 updates
const recentUpdates = await user.getAuditLogs({ action: "updated", limit: 5 });4. Inspect the audit_logs Table
After performing the operations above, the audit_logs table contains entries like:
| id | action | entity_type | entity_id | old_values | new_values | created_at |
|---|---|---|---|---|---|---|
| uuid-1 | created | User | abc-123 | null | {"name":"Alice","email":"alice@example.com","role":"member"} | 2025-01-15 10:00:00 |
| uuid-2 | updated | User | abc-123 | {"name":"Alice"} | {"name":"Bob"} | 2025-01-15 10:05:00 |
| uuid-3 | deleted | User | abc-123 | {"name":"Bob","email":"alice@example.com","role":"member"} | null | 2025-01-15 10:10:00 |
Note that passwordHash does not appear in any of the recorded values because it was listed in the except option.
What Triggers Audit Logging
| Operation | Triggers Audit Log |
|---|---|
repository.save(entity) | Yes |
repository.remove(entity) | Yes |
entity.save() (ActiveRecord) | Yes |
entity.remove() (ActiveRecord) | Yes |
repository.update(id, data) | No |
repository.delete(id) | No |
queryBuilder.update().execute() | No |
queryBuilder.delete().execute() | No |
For operations that do not trigger automatic logging, use AuditLogService.log() to create entries manually. See Manual Logging.
Next Steps
- Configuration -- explore all module configuration options.
- Auditable Entities -- learn about the
@Auditable()decorator andAuditableMixinin detail. - Actor Resolution -- track which user made each change.