@zola_do/audit
v0.2.9
Published
RabbitMQ-based audit logging for NestJS
Maintainers
Readme
@zola_do/audit
RabbitMQ-based audit logging for NestJS applications with entity change tracking and request auditing.
Overview
@zola_do/audit provides comprehensive audit logging capabilities:
- Request Auditing — Automatic logging of HTTP requests
- Event Auditing — Mark RPC events with
@AuditRmqEvent() - Entity Auditing — TypeORM subscriber for INSERT/UPDATE/DELETE
- Conditional Loading — Gracefully degrades without RabbitMQ
- User Context — Captures user and organization from request
Installation
# Install individually
npm install @zola_do/audit
# Or via meta package
npm install @zola_do/nestjs-sharedRequired Dependencies
For full audit functionality:
npm install @nestjs/microservices amqplibNote: The module loads successfully without these dependencies but audit features are disabled with a development warning.
Quick Start
1. Configure Environment
# .env
RMQ_URL=amqp://user:pass@localhost:5672
APPLICATION_NAME=my-app2. Register Module
import { Module } from '@nestjs/common';
import { AuditModule } from '@zola_do/audit';
@Module({
imports: [AuditModule],
})
export class AppModule {}3. Mark Routes for Audit
import { Controller, Post, Body } from '@nestjs/common';
import { AuditRmqEvent } from '@zola_do/audit';
@Controller('orders')
export class OrdersController {
@Post()
@AuditRmqEvent()
createOrder(@Body() dto: CreateOrderDto) {
// This request is logged to RabbitMQ
return this.orderService.create(dto);
}
}Audit Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Audit Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ HTTP Request │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ JwtGuard │──► request.user │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ AuditLoggerInterceptor (Global) │ │
│ │ │ │
│ │ Checks @AuditRmqEvent() metadata │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ │ @AuditRmqEvent() found │
│ ▼ │
│ ┌─────────────┐ │
│ │ Publish to │ │
│ │ RabbitMQ │─────► Exchange: audit.events │
│ └─────────────┘ Queue: audit.logs │
│ │
└─────────────────────────────────────────────────────────────────────┘Decorators
@AuditRmqEvent()
Marks a controller method for audit logging:
@Controller('orders')
export class OrdersController {
@Post()
@AuditRmqEvent()
createOrder(@Body() dto: CreateOrderDto) {
// Logs: { userId, organization, method, path, body, timestamp }
}
@Put(':id')
@AuditRmqEvent({ action: 'UPDATE_ORDER' })
updateOrder(@Param('id') id: string, @Body() dto: UpdateOrderDto) {
// Logs with custom action name
}
}@IgnoreLogger()
Excludes specific routes from audit logging:
@Controller()
export class HealthController {
@Get('health')
@IgnoreLogger()
healthCheck() {
return { status: 'ok' };
}
@Get('metrics')
@IgnoreLogger()
metrics() {
return { requests: 1000 };
}
}Entity Auditing
The AuditSubscriber automatically tracks entity changes:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuditSubscriber } from '@zola_do/audit';
@Module({
imports: [
TypeOrmModule.forRoot({
subscribers: [AuditSubscriber],
}),
],
})
export class AppModule {}Tracked Events
| Event | Description |
| -------- | --------------- |
| INSERT | Entity created |
| UPDATE | Entity modified |
| DELETE | Entity deleted |
Audit Data Structure
interface AuditLog {
id: string;
userId?: string;
organizationId?: string;
action: string;
entity: string;
entityId: string;
oldValues?: Record<string, any>;
newValues?: Record<string, any>;
timestamp: Date;
requestPath?: string;
requestMethod?: string;
}Configuration
RabbitMQ Config
import { auditLoggerConfig } from '@zola_do/audit';
// Uses RMQ_URL from environment
export const rmqConfig = auditLoggerConfig;Manual Audit Publishing
import { Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Inject } from '@nestjs/common';
import { AUDIT_RMQ_EVENT } from '@zola_do/audit';
@Injectable()
export class OrderService {
constructor(
@Inject('AUDIT_CLIENT') private readonly auditClient: ClientProxy,
) {}
async createOrder(dto: CreateOrderDto, user: any) {
const order = await this.orderRepo.save(dto);
// Publish audit event
this.auditClient.emit(AUDIT_RMQ_EVENT, {
userId: user.id,
organizationId: user.organizationId,
action: 'ORDER_CREATED',
entity: 'Order',
entityId: order.id,
newValues: order,
timestamp: new Date(),
});
return order;
}
}Environment Variables
| Variable | Description | Required |
| ------------------ | ----------------------- | ------------------------ |
| RMQ_URL | RabbitMQ connection URL | Yes (for audit features) |
| APPLICATION_NAME | App name for audit logs | No |
Conditional Loading
The module gracefully handles missing dependencies:
// If amqplib or @nestjs/microservices not installed:
@Module({
providers: [AuditModule],
exports: [AuditModule],
})
export class AuditModule {
// AuditLoggerInterceptor NOT registered
// Warning in development: "Audit logging disabled - missing dependencies"
}
// If dependencies installed:
@Module({
providers: [AuditLoggerInterceptor, AuditSubscriber],
exports: [AuditModule],
})
export class AuditModule {
// Full audit functionality enabled
}DTOs
CreateAuditLogDto
interface CreateAuditLogDto {
userId?: string;
organizationId?: string;
action: string;
entity: string;
entityId: string;
oldValues?: Record<string, any>;
newValues?: Record<string, any>;
requestPath?: string;
requestMethod?: string;
metadata?: Record<string, any>;
}CreateEventDTO
interface CreateEventDTO {
userId?: string;
organizationId?: string;
eventName: string;
eventData: Record<string, any>;
timestamp?: Date;
}API Reference
Module
@Module({
imports: [AuditModule],
// Or register subscriber globally
})
export class AppModule {}Decorators
| Decorator | Description |
| ---------------------------- | ---------------------- |
| @AuditRmqEvent() | Mark for audit logging |
| @AuditRmqEvent({ action }) | Custom action name |
| @IgnoreLogger() | Exclude from audit |
Exports
// All exports from index.ts
import {
AuditModule,
AuditRmqEvent,
AUDIT_RMQ_EVENT,
IgnoreLogger,
IGNORE_AUDIT_LOGGER,
AuditLoggerInterceptor,
AuditSubscriber,
auditLoggerConfig,
} from '@zola_do/audit';Best Practices
1. Audit Sensitive Operations
@Post(':id/approve')
@AuditRmqEvent({ action: 'ORDER_APPROVED' })
async approveOrder(@Param('id') id: string) {
return this.orderService.approve(id);
}
@Post(':id/reject')
@AuditRmqEvent({ action: 'ORDER_REJECTED' })
async rejectOrder(@Param('id') id: string) {
return this.orderService.reject(id);
}2. Exclude Health Checks
@Controller()
export class HealthController {
@Get()
@IgnoreLogger() // Don't audit health checks
health() {
return { status: 'ok' };
}
}3. Use Custom User Extraction
The interceptor extracts user from request.user (populated by JwtGuard):
// Ensure your JwtStrategy sets user object
class JwtStrategy extends PassportStrategy(Strategy) {
async validate(payload: any) {
return {
id: payload.sub,
email: payload.email,
organizationId: payload.organization?.organizationId,
};
}
}Troubleshooting
Q: Audit events not being published?
- Check
RMQ_URLis configured - Verify RabbitMQ is running
- Check for
Audit logging disabledwarning in logs
Q: UserId is undefined in audit?
Ensure JwtGuard or AuthorizationModule is enabled and populates request.user.
Q: Module loading fails?
Install optional dependencies or ignore the warning - the module still loads without audit functionality.
Related Packages
- @zola_do/authorization — User context for audit
License
ISC
