@nestbolt/audit-log
Querying
Retrieve and filter audit log entries by entity, actor, date range, and action type.
The AuditLogService provides three query methods for retrieving audit log entries. You can also query logs directly from entity instances when using AuditableMixin.
AuditLogService Query Methods
getAuditLogs(entityType, entityId, options?)
Retrieves audit log entries for a specific entity, ordered by createdAt descending (most recent first).
import { Injectable } from "@nestjs/common";
import { AuditLogService } from "@nestbolt/audit-log";
@Injectable()
export class AuditService {
constructor(private readonly auditLogService: AuditLogService) {}
async getHistory(entityType: string, entityId: string) {
// Get all audit logs for an entity
const logs = await this.auditLogService.getAuditLogs("User", userId);
// With filtering options
const filtered = await this.auditLogService.getAuditLogs("User", userId, {
action: "updated",
from: new Date("2025-01-01"),
to: new Date("2025-12-31"),
limit: 20,
offset: 0,
});
return filtered;
}
}Parameters:
| Parameter | Type | Description |
|---|---|---|
entityType | string | The entity type name (as set by @Auditable() or the class name) |
entityId | string | The entity's unique identifier |
options | AuditLogQueryOptions | Optional filtering and pagination |
Returns: Promise<AuditLogEntity[]>
getAuditLogsByActor(actorType, actorId, options?)
Retrieves all audit log entries created by a specific actor, ordered by createdAt descending. This is useful for building user activity feeds or admin action reports.
// Get all actions performed by a specific admin
const adminActions = await this.auditLogService.getAuditLogsByActor("Admin", adminId);
// Get only the last 50 actions by a user in the past month
const recentActions = await this.auditLogService.getAuditLogsByActor("User", userId, {
from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
limit: 50,
});
// Get only delete actions by a specific actor
const deletions = await this.auditLogService.getAuditLogsByActor("Admin", adminId, {
action: "deleted",
});Parameters:
| Parameter | Type | Description |
|---|---|---|
actorType | string | The actor type (e.g., "User", "Admin", "System") |
actorId | string | The actor's unique identifier |
options | AuditLogQueryOptions | Optional filtering and pagination |
Returns: Promise<AuditLogEntity[]>
getLatestAuditLog(entityType, entityId)
Returns the single most recent audit log entry for a specific entity, or null if no entries exist.
const latest = await this.auditLogService.getLatestAuditLog("User", userId);
if (latest) {
console.log(`Last modified: ${latest.createdAt}`);
console.log(`Action: ${latest.action}`);
console.log(`By: ${latest.actorType} ${latest.actorId}`);
}Parameters:
| Parameter | Type | Description |
|---|---|---|
entityType | string | The entity type name |
entityId | string | The entity's unique identifier |
Returns: Promise<AuditLogEntity | null>
Query Options
All query methods that accept options use the AuditLogQueryOptions interface:
interface AuditLogQueryOptions {
action?: "created" | "updated" | "deleted";
from?: Date;
to?: Date;
limit?: number;
offset?: number;
}| Option | Type | Description |
|---|---|---|
action | "created" | "updated" | "deleted" | Filter by action type |
from | Date | Include only logs created at or after this date |
to | Date | Include only logs created at or before this date |
limit | number | Maximum number of entries to return |
offset | number | Number of entries to skip (for pagination) |
All options are optional. When no options are provided, all matching entries are returned ordered by createdAt descending.
Pagination Example
async getAuditLogPage(
entityType: string,
entityId: string,
page: number,
pageSize: number,
) {
return this.auditLogService.getAuditLogs(entityType, entityId, {
limit: pageSize,
offset: (page - 1) * pageSize,
});
}
// Usage
const page1 = await this.getAuditLogPage("User", userId, 1, 20); // First 20 entries
const page2 = await this.getAuditLogPage("User", userId, 2, 20); // Entries 21-40Date Range Example
// Get all changes made today
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayLogs = await this.auditLogService.getAuditLogs("Order", orderId, {
from: today,
to: new Date(),
});
// Get all changes made in Q1 2025
const q1Logs = await this.auditLogService.getAuditLogs("Order", orderId, {
from: new Date("2025-01-01"),
to: new Date("2025-03-31T23:59:59.999Z"),
});Querying via Entity Instances
When your entity extends AuditableMixin, you can query audit logs directly from entity instances without needing to inject AuditLogService:
const user = await userRepo.findOneByOrFail({ id: userId });
// Get all audit logs for this entity
const logs = await user.getAuditLogs();
// Get filtered logs
const updates = await user.getAuditLogs({
action: "updated",
limit: 10,
});
// Get the most recent log
const latest = await user.getLatestAuditLog();The mixin methods internally use AuditLogService, so the module must be initialized. If you call these methods before AuditLogModule is registered, an AuditLogNotInitializedException is thrown.
The AuditLogEntity
Each audit log entry is stored as an AuditLogEntity with the following properties:
class AuditLogEntity {
id: string; // UUID primary key
action: string; // "created", "updated", or "deleted"
entityType: string; // Entity type name
entityId: string; // Entity ID
actorType: string | null; // Actor type (e.g., "User", "Admin", "System")
actorId: string | null; // Actor ID
oldValues: Record<string, any> | null; // Previous field values (null for "created")
newValues: Record<string, any> | null; // New field values (null for "deleted")
metadata: Record<string, any> | null; // Extra context
ipAddress: string | null; // Request IP address
userAgent: string | null; // Request user agent
createdAt: Date; // When the audit entry was created
}Database Columns
The entity maps to the audit_logs table with the following column definitions:
| Column | Type | Nullable | Description |
|---|---|---|---|
id | UUID | No | Auto-generated primary key |
action | VARCHAR(50) | No | The action type |
entity_type | VARCHAR(255) | No | Entity type name |
entity_id | VARCHAR(255) | No | Entity identifier |
actor_type | VARCHAR(255) | Yes | Actor type |
actor_id | VARCHAR(255) | Yes | Actor identifier |
old_values | JSON | Yes | Previous field values as JSON |
new_values | JSON | Yes | New field values as JSON |
metadata | JSON | Yes | Extra context as JSON |
ip_address | VARCHAR(45) | Yes | IPv4 or IPv6 address |
user_agent | VARCHAR(512) | Yes | Browser/client user agent |
created_at | TIMESTAMP | No | Auto-generated creation timestamp |
Database Indexes
The table includes indexes for efficient querying:
| Index | Columns | Purpose |
|---|---|---|
| Primary key | id | Unique entry lookup |
| Entity index | entity_type, entity_id | Fast lookup by entity |
| Actor index | actor_type, actor_id | Fast lookup by actor |
| Action index | action | Filter by action type |
| Created at index | created_at | Date range queries and ordering |
Building an Audit Log API Endpoint
Here is a complete example of a controller that exposes audit log queries via REST:
import { Controller, Get, Param, Query } from "@nestjs/common";
import { AuditLogService, AuditLogEntity } from "@nestbolt/audit-log";
@Controller("audit-logs")
export class AuditLogController {
constructor(private readonly auditLogService: AuditLogService) {}
@Get("entity/:entityType/:entityId")
async getEntityLogs(
@Param("entityType") entityType: string,
@Param("entityId") entityId: string,
@Query("action") action?: "created" | "updated" | "deleted",
@Query("from") from?: string,
@Query("to") to?: string,
@Query("limit") limit?: string,
@Query("offset") offset?: string,
): Promise<AuditLogEntity[]> {
return this.auditLogService.getAuditLogs(entityType, entityId, {
action,
from: from ? new Date(from) : undefined,
to: to ? new Date(to) : undefined,
limit: limit ? parseInt(limit, 10) : undefined,
offset: offset ? parseInt(offset, 10) : undefined,
});
}
@Get("actor/:actorType/:actorId")
async getActorLogs(
@Param("actorType") actorType: string,
@Param("actorId") actorId: string,
@Query("action") action?: "created" | "updated" | "deleted",
@Query("limit") limit?: string,
@Query("offset") offset?: string,
): Promise<AuditLogEntity[]> {
return this.auditLogService.getAuditLogsByActor(actorType, actorId, {
action,
limit: limit ? parseInt(limit, 10) : undefined,
offset: offset ? parseInt(offset, 10) : undefined,
});
}
@Get("entity/:entityType/:entityId/latest")
async getLatest(
@Param("entityType") entityType: string,
@Param("entityId") entityId: string,
): Promise<AuditLogEntity | null> {
return this.auditLogService.getLatestAuditLog(entityType, entityId);
}
}Next Steps
- Actor Resolution -- learn how to track which user or system made each change.
- Events -- react to audit log creation with event listeners.