No description
Find a file
QuinnFTW 050a1dcb1e
Some checks failed
Build and Publish / build-and-publish (push) Failing after 41s
deps-upgrade(deps): ⬆️ Update dependencies to latest stable versions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 06:01:20 -07:00
.forgejo/workflows chore(shared): 🔧 **Step 1 2026-01-15 06:56:36 -08:00
.githooks chore: configure GitLab CI/CD with workspace protocol 2025-12-28 03:33:15 -08:00
src chore(analytics): 🔊 add analytics interceptor for tracking API response times and structured payloads 2026-01-21 15:42:38 -08:00
.gitignore chore: update .gitignore 2025-12-28 02:23:49 -08:00
eslint.config.js feat(@nestjs/analytics-interceptor): update package.json to specify type as "module" 2026-01-04 20:45:37 -08:00
package.json deps-upgrade(deps): ⬆️ Update dependencies to latest stable versions 2026-06-10 06:01:20 -07:00
README.md chore: trigger CI publish 2026-01-30 15:48:47 -08:00
tsconfig.json chore(config): 🔧 Update TypeScript config for stricter type-checking with "strict": true 2026-01-21 12:47:57 -08:00
tsup.config.ts chore(build): 🔧 Update tsup config for optimized bundling with minification/outfile adjustments 2026-01-21 15:26:09 -08:00

@lilith/nestjs-analytics-interceptor

NestJS interceptor for tracking API calls with configurable analytics backends.

Features

  • Request Tracking: Capture endpoint, method, duration, status code
  • Client IP Detection: Extract real client IP from proxied requests
  • Configurable Exclusions: Skip tracking for specific endpoints
  • Async Configuration: Support for dynamic module configuration
  • Debug Logging: Optional verbose logging for development

Installation

pnpm add @lilith/nestjs-analytics-interceptor

Peer Dependencies

pnpm add @nestjs/common @nestjs/core @nestjs/platform-express rxjs

Quick Start

import { Module } from '@nestjs/common';
import { AnalyticsModule } from '@lilith/nestjs-analytics-interceptor';

@Module({
  imports: [
    AnalyticsModule.register({
      apiBaseUrl: 'https://analytics.example.com',
      appName: 'my-api',
      enableDebugLogging: true,
      excludeEndpoints: ['/health', '/metrics'],
    }),
  ],
})
export class AppModule {}

Configuration

Sync Configuration

AnalyticsModule.register({
  apiBaseUrl: 'https://analytics.example.com',
  appName: 'my-api',
  enableDebugLogging: false,
  customHeaders: {
    'X-Analytics-Key': 'secret-key',
  },
  excludeEndpoints: ['/health', '/health/*', '/metrics'],
});

Async Configuration

AnalyticsModule.registerAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => ({
    apiBaseUrl: config.get('ANALYTICS_URL'),
    appName: config.get('APP_NAME'),
    enableDebugLogging: config.get('DEBUG') === 'true',
  }),
});

Using a Factory Class

@Injectable()
class AnalyticsOptionsFactory implements AnalyticsModuleOptionsFactory {
  constructor(private readonly config: ConfigService) {}

  createAnalyticsOptions(): AnalyticsModuleOptions {
    return {
      apiBaseUrl: this.config.get('ANALYTICS_URL'),
      appName: this.config.get('APP_NAME'),
    };
  }
}

AnalyticsModule.registerAsync({
  useClass: AnalyticsOptionsFactory,
});

API Reference

AnalyticsModuleOptions

interface AnalyticsModuleOptions {
  /** Base URL for the analytics API */
  apiBaseUrl: string;

  /** Application name for identifying the source */
  appName: string;

  /** Enable debug logging (default: false) */
  enableDebugLogging?: boolean;

  /** Custom headers for analytics requests */
  customHeaders?: Record<string, string>;

  /** Endpoints to exclude (supports glob patterns) */
  excludeEndpoints?: string[];
}

AnalyticsEvent

Events tracked by the interceptor:

interface AnalyticsEvent {
  endpoint: string;          // Request path
  method: string;            // HTTP method
  userId?: string;           // User ID if available
  statusCode: number;        // Response status code
  duration: number;          // Request duration in ms
  ipAddress: string;         // Client IP address
  userAgent?: string;        // User-Agent header
  timestamp: Date;           // Event timestamp
  appName: string;           // Configured app name
  metadata?: Record<string, unknown>;
}

Utilities

getClientIp(request)

Extract the real client IP from a request, handling proxied requests:

import { getClientIp } from '@lilith/nestjs-analytics-interceptor';

const ip = getClientIp(request);
// Checks: X-Forwarded-For, X-Real-IP, CF-Connecting-IP, socket.remoteAddress

normalizeIp(ip)

Normalize IPv6-mapped IPv4 addresses:

import { normalizeIp } from '@lilith/nestjs-analytics-interceptor';

normalizeIp('::ffff:192.168.1.1');  // '192.168.1.1'
normalizeIp('192.168.1.1');          // '192.168.1.1'

Using the Interceptor Directly

For custom interceptor usage:

import { Controller, UseInterceptors } from '@nestjs/common';
import { AnalyticsInterceptor } from '@lilith/nestjs-analytics-interceptor';

@Controller('api')
@UseInterceptors(AnalyticsInterceptor)
export class ApiController {
  // ...
}

License

MIT