@pawells/nestjs-shared
v2.0.2
Published
Shared NestJS infrastructure: filters, logging, metrics, and security
Readme
NestJS Shared Module
Foundational NestJS infrastructure library providing filters, guards, interceptors, logging, CSRF protection, error handling, configuration, metrics, and lazy loading utilities.
Installation
yarn add @pawells/nestjs-sharedRequirements
- Node.js: >= 22.0.0
- NestJS: >= 10.0.0
Peer Dependencies
{
"@nestjs/common": ">=10.0.0",
"@nestjs/config": ">=3.0.0",
"@nestjs/core": ">=10.0.0",
"@nestjs/throttler": ">=5.0.0",
"@opentelemetry/api": ">=1.0.0",
"class-transformer": ">=0.5.0",
"class-validator": ">=0.14.0",
"compression": ">=1.0.0",
"csrf-csrf": ">=4.0.0",
"express": ">=5.0.0",
"helmet": ">=7.0.0",
"joi": ">=18.0.0",
"prom-client": ">=15.0.0",
"rxjs": ">=7.0.0",
"xss": ">=1.0.0"
}Quick Start
1. Initialize ConfigModule (MUST come first)
import { Module } from '@nestjs/common';
import { ConfigModule } from '@pawells/nestjs-shared';
@Module({
imports: [
ConfigModule, // Must be first
],
})
export class AppModule {}2. Import CommonModule
import { Module } from '@nestjs/common';
import { CommonModule } from '@pawells/nestjs-shared';
@Module({
imports: [
ConfigModule, // Must be first
CommonModule, // Depends on ConfigModule
],
})
export class AppModule {}3. Use Global Filters and Interceptors
Global filters and interceptors are automatically registered by CommonModule:
- GlobalExceptionFilter & HttpExceptionFilter: Standardized error responses
- LoggingInterceptor: Request/response logging
- HTTPMetricsInterceptor: HTTP metrics collection
- ValidationPipe: Automatic DTO validation
All are applied globally and available for injection in services.
Features
Error Handling
Comprehensive error handling with structured responses, categorization, and sanitization.
import {
BaseApplicationError,
GlobalExceptionFilter,
ErrorCategorizerService,
ErrorSanitizerService,
} from '@pawells/nestjs-shared';
// Create custom error
export class UserNotFoundError extends BaseApplicationError {
constructor(userId: string) {
super(`User ${userId} not found`, {
code: 'USER_NOT_FOUND',
statusCode: 404,
context: { userId }
});
}
}
// Throw it
throw new UserNotFoundError('123');
// Automatically caught by GlobalExceptionFilter
// Response includes code, message, timestamp, sanitized context, stack (dev only)Error Categories: Errors are automatically categorized as transient (retryable) or permanent (fail-fast) with recommended recovery strategies.
Logging
Centralized, structured logging with automatic redaction of sensitive data.
import { AppLogger } from '@pawells/nestjs-shared';
constructor(private logger: AppLogger) {}
// Flexible logging methods
this.logger.info('User created', 'UserService', { userId: '123' });
this.logger.debug('Cache hit', { metadata: { key: 'users:123' } });
this.logger.error('Database error', error.stack, { query: '...' });
this.logger.warn('Rate limit approaching', { context: 'RateLimiter' });
this.logger.fatal('System shutdown', { context: 'ShutdownService' });
// Automatic redaction of sensitive fields: passwords, tokens, API keys, emails, IPs
this.logger.info('Login attempt', { password: 'secret' });
// Logs: { password: '[REDACTED]' }
// OpenTelemetry integration: traceId and spanId automatically includedLog Levels: Controlled by LOG_LEVEL environment variable (debug, info, warn, error, fatal, silent).
Metrics
Prometheus metrics for HTTP requests and custom metrics.
import { MetricsRegistryService } from '@pawells/nestjs-shared';
constructor(private metrics: MetricsRegistryService) {}
// Record custom metrics
const orderCounter = this.metrics.CreateCounter(
'orders_total',
'Total orders processed'
);
orderCounter.inc({ status: 'completed' });
// HTTP metrics are automatic (duration, count, size)
// Access at GET /metrics in Prometheus format
const registry = this.metrics.GetRegistry();
const prometheusMetrics = await registry.metrics();Features:
- Automatic HTTP request metrics (duration histogram, request counter, size histogram)
- Dynamic path normalization (UUIDs, ObjectIDs, numeric IDs →
:id) to prevent unbounded cardinality - Default Node.js metrics collection
- Custom metric creation (counter, gauge, histogram)
- Controlled by METRICS_ENABLED environment variable (default: true)
CSRF Protection
Double-Submit Cookie pattern with per-IP rate limiting.
import { CSRFGuard, CSRFService } from '@pawells/nestjs-shared';
// Globally applied by CommonModule
// Or manually on specific controller
@UseGuards(CSRFGuard)
@Controller('api')
export class ApiController {
constructor(private csrf: CSRFService) {}
@Post('/form')
async submitForm(@Req() req: Request, @Res() res: Response) {
// Generate token
const token = await this.csrf.GenerateToken(req, res);
res.render('form', { csrfToken: token });
}
@Post('/process')
async processForm(@Req() req: Request) {
// Validation done automatically by CSRFGuard
// Safe methods (GET, HEAD, OPTIONS) bypass validation
}
}Features:
- Token generation with rate limiting (10 per IP per 60s)
- Session binding or IP-based fallback
- Automatic pruning of stale timestamps
- Capacity monitoring with safety margins
- Configurable proxy trust for X-Forwarded-For header
- CSRF_SECRET entropy validation at startup
Validation
Automatic DTO validation and transformation.
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty()
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(8)
password: string;
}
@Post()
async createUser(@Body() dto: CreateUserDto) {
// DTO automatically validated and transformed
// Validation errors formatted as error array with field paths
}Features:
- Automatic DTO validation via ValidationPipe
- Nested object support with path prefixes
- Comprehensive error formatting
- Class transformation with class-transformer
Health Checks
Kubernetes-ready health, readiness, and liveness probes.
import { HealthCheckService, HealthStatus } from '@pawells/nestjs-shared';
constructor(private health: HealthCheckService) {}
@Get('/health')
getHealth() {
return this.health.GetHealth('my-service', '1.0.0');
}
@Get('/ready')
getReadiness() {
return this.health.GetReadiness({
database: HealthStatus.OK,
cache: HealthStatus.OK,
});
}
@Get('/live')
getLiveness() {
return this.health.GetLiveness();
}Configuration
Type-safe environment variable access with validation.
import { ConfigService } from '@pawells/nestjs-shared';
constructor(private config: ConfigService) {}
// Type-safe getters
const port = this.config.GetNumber('PORT') ?? 3000;
const nodeEnv = this.config.GetString('NODE_ENV') ?? 'development';
const dbUrl = this.config.GetOrThrow('DATABASE_URL');
// Validation
this.config.Validate({
PORT: { required: true },
DATABASE_URL: { required: true },
LOG_LEVEL: { required: false },
});Audit Logging
Security event logging for compliance and forensics.
import { AuditLoggerService } from '@pawells/nestjs-shared';
constructor(private audit: AuditLoggerService) {}
// Log authentication
this.audit.LogAuthenticationAttempt('[email protected]', true, '192.168.1.1');
// Log authorization failure
this.audit.LogAuthorizationFailure('user-123', 'documents', 'delete', '192.168.1.1');
// Log CSRF violations
this.audit.LogCsrfViolation('192.168.1.1', '/api/users');
// Log custom security events
this.audit.LogSecurityEvent({
userId: 'user-123',
action: 'password_change',
resource: 'users/123',
result: 'success',
ipAddress: '192.168.1.1'
});Lazy Loading
Defer dependency resolution to avoid circular dependencies.
import {
TLazyGetter,
ILazyModuleRefService,
createMemoizedLazyGetter,
} from '@pawells/nestjs-shared';
import { ModuleRef } from '@nestjs/core';
@Injectable()
export class MyService implements ILazyModuleRefService {
private readonly userService: TLazyGetter<UserService> = createMemoizedLazyGetter(
() => this.Module.get(UserService, { strict: false }),
);
constructor(public readonly Module: ModuleRef) {}
async getUser(id: string) {
// UserService resolved lazily on first call
return this.userService().getById(id);
}
}Alternatively, extend LazyModuleRefBase to eliminate constructor boilerplate:
import { LazyModuleRefBase } from '@pawells/nestjs-shared';
@Injectable()
export class MyService extends LazyModuleRefBase {
protected get UserService(): UserService {
return this.Module.get(UserService);
}
}Features:
TLazyGetter<T>: Type alias for a required dependency getter functionTOptionalLazyGetter<T>: Type alias for an optional dependency getter functioncreateMemoizedLazyGetter: Factory that caches the resolved dependency after first callcreateOptionalLazyGetter: Safe resolution that returnsundefinedinstead of throwingisLazyModuleRefService: Type guard for pattern detectionLazyModuleRefBase: Abstract base class to avoid constructor boilerplate
HTTP Client
Robust HTTP client with timeout, SSL/TLS, and sensitive data redaction.
import { HttpClientService } from '@pawells/nestjs-shared';
constructor(private http: HttpClientService) {}
// GET request
const response = await this.http.Get<User>('https://api.example.com/users/123');
// POST request with custom timeout
const response = await this.http.Post<User>(
'https://api.example.com/users',
{ name: 'John', email: '[email protected]' },
{ timeout: 5000, correlationId: 'req-123' }
);
// HTTPS with custom CA certificate
const cert = fs.readFileSync('/path/to/ca.pem');
const response = await this.http.Get(
'https://internal-api.local/data',
{ ca: cert }
);Features:
- Configurable timeouts (default: HTTP_CLIENT_TIMEOUT)
- SSL/TLS certificate validation (strict by default)
- Custom CA certificate support
- Payload size limit (10MB)
- Automatic content-type parsing
- Correlation ID support
- Sensitive data redaction in logs
- Duration tracking
Decorators
Request property extractors for cleaner controller methods.
import { Query, Params, Body, Headers, Cookies } from '@pawells/nestjs-shared';
@Controller('users')
export class UserController {
@Get(':id')
getUser(
@Params('id') id: string,
@Query('include') include?: string,
) {
// Route params and query params extracted
}
@Post()
createUser(
@Body() dto: CreateUserDto,
@Headers('authorization') auth?: string,
) {
// Body and headers extracted
}
@Get()
listUsers(@Cookies('sessionId') sessionId?: string) {
// Cookies extracted
}
}Core Modules
CommonModule
Global module providing all shared infrastructure.
// Automatically applied globally
@Global()
@Module({
providers: [
// Filters (global)
GlobalExceptionFilter,
HttpExceptionFilter,
// Interceptors (global)
LoggingInterceptor,
HTTPMetricsInterceptor,
// Pipe (global)
ValidationPipe,
// Services (injectable)
AppLogger,
AuditLoggerService,
CSRFService,
ErrorCategorizerService,
ErrorSanitizerService,
HttpClientService,
MetricsRegistryService,
HealthCheckService,
],
exports: [...],
})
export class CommonModule {}ConfigModule
Configuration service with validation.
@Module({
imports: [ConfigModule],
// ConfigService and ValidationService automatically available
})
export class AppModule {}MetricsModule
Optional module that exposes a Prometheus /metrics endpoint.
@Module({
imports: [
ConfigModule,
CommonModule,
MetricsModule.ForRoot(), // Opt-in: adds GET /metrics endpoint
],
})
export class AppModule {}
// Provides:
// - MetricsRegistryService (injectable)
// - GET /metrics endpoint (Prometheus format)API Reference
Services
AppLogger
Structured logging service with context support, metadata, and automatic sensitive data redaction.
Methods:
Debug(message, context?, metadata?): Debug level (primary PascalCase method)debug(message, context?, metadata?): Lowercase alias forDebuginfo(message, context?, metadata?): Info levelwarn(message, context?, metadata?): Warning levelerror(message, trace?, context?, metadata?): Error level with optional stack tracefatal(message, trace?, context?, metadata?): Fatal level with optional stack traceCreateContextualLogger(context): Create a child logger bound to a context stringcreateContextualLogger(context): Lowercase alias, satisfies theIContextualLoggerinterface
All methods accept either positional parameters or a single ILogOptions object { context?, trace?, metadata? } as the second argument.
NestLoggerAdapter
Adapts AppLogger to the NestJS LoggerService interface. Pass an instance to NestFactory.create() to route all NestJS framework logs through AppLogger.
import { NestLoggerAdapter } from '@pawells/nestjs-shared';
const app = await NestFactory.create(AppModule, {
logger: new NestLoggerAdapter(),
});The adapter provides both PascalCase methods (Log, Error, Warn, Debug, Verbose, Fatal) and the lowercase aliases (log, error, warn) required by the NestJS LoggerService interface.
ErrorCategorizerService
Classifies errors as transient/permanent and recommends recovery strategy.
Methods:
CategorizeError(error): ReturnsIErrorCategorywithtype,retryable,strategy,backoffMsIsRetryable(error): Boolean checkLogRecoveryAttempt(error, attempt, maxAttempts): Log retry attemptLogRecoverySuccess(error, attempts): Log successful recoveryLogRecoveryFailed(error, attempts): Log failed recovery
ErrorSanitizerService
Removes sensitive information from error responses before they are sent to clients.
Methods:
SanitizeErrorResponse(error, isDevelopment?): Sanitize error object for client response; strips stack traces in production and redacts sensitive field values
Redactions applied to messages: file paths, database URIs, API keys, Bearer tokens, email addresses, IP addresses. Redactions applied to context objects: field names matching sensitive patterns (password, token, secret, key, etc.).
CSRFService
CSRF token generation and validation with rate limiting.
Methods:
GenerateToken(req, res): Generate and set CSRF token; rate-limited to 10 per IP per 60 secondsValidateToken(req): Validate CSRF token; returnstrueif validRefreshToken(req, res): Generate a fresh token after sensitive operations (login, password change)GetMiddleware(): Return the underlyingdoubleCsrfmiddleware function
MetricsRegistryService
Prometheus metrics management.
Methods:
RecordHttpRequest(method, route, statusCode, duration, size?): Record HTTP request metricsCreateCounter(name, help, labelNames?): Create and register a counter metricCreateGauge(name, help, labelNames?): Create and register a gauge metricCreateHistogram(name, help, labelNames?, buckets?): Create and register a histogram metricRecordCounter(name, value?, labels?): Increment a registered counter by nameRecordGauge(name, value, labels?): Set a registered gauge value by nameRecordHistogram(name, value, labels?): Observe a registered histogram value by nameGetRegistry(): Return the underlying PrometheusRegistryinstance
HealthCheckService
Kubernetes health probes.
Methods:
GetHealth(serviceName?, version?): General health checkGetReadiness(checks?): Readiness probe (can the service receive traffic)GetLiveness(): Liveness probe (is the service alive)
HttpClientService
HTTP client with timeout and SSL/TLS support.
Methods:
Request<T>(options): Make an HTTP request with full options controlGet<T>(url, options?): GET requestPost<T>(url, data?, options?): POST requestPut<T>(url, data?, options?): PUT requestDelete<T>(url, options?): DELETE request
ConfigService
Type-safe environment variable access.
Methods:
Get<T>(propertyPath, defaultValue?): Return config value or defaultGetOrThrow<T>(propertyPath): Return config value or throw if not setGetString(propertyPath, defaultValue?): Return value coerced to stringGetNumber(propertyPath, defaultValue?): Return value coerced to numberValidate(schema): Validate required fields against schema; throws on missing required keys
AuditLoggerService
Security event logging.
Methods:
LogAuthenticationAttempt(email, success, ipAddress?, reason?)LogAuthorizationFailure(userId, resource, action, ipAddress?)LogTokenGeneration(userId, tokenType)LogTokenRevocation(userId, reason)LogRateLimitViolation(endpoint, ipAddress, limit)LogCsrfViolation(ipAddress, endpoint)LogConfigurationChange(userId, config, oldValue, newValue)LogDataAccess(userId, resource, action)LogSecurityEvent(entry)
Filters
GlobalExceptionFilter
Catches unhandled exceptions (except HttpException).
- Standardizes response format
- Sanitizes sensitive data
- Categorizes errors
- Logs with context
HttpExceptionFilter
Handles HTTP exceptions.
- Formats NestJS built-in exceptions
- Sanitizes responses
- Categorizes for logging
Interceptors
LoggingInterceptor
Logs incoming requests and outgoing responses.
- Uses DEBUG level for /health and /metrics
- Uses INFO level for other requests
- Includes method, URL, IP, duration
HTTPMetricsInterceptor
Collects HTTP request metrics.
- Duration histogram
- Request counter
- Request size histogram
- Automatic route normalization
Guards
CSRFGuard
Validates CSRF tokens on state-changing requests.
- Bypasses safe methods (GET, HEAD, OPTIONS)
- Enforces validation for POST/PUT/DELETE/PATCH
- Throws 403 on validation failure
- Logs violations to audit log
Pipes
ValidationPipe
Validates DTOs using class-validator.
- Automatic transformation via class-transformer
- Nested object support with path prefixes
- Comprehensive error formatting
Interfaces & Types
ILogger
Basic logging interface without contextual logger creation.
IContextualLogger
Extended logging with contextual logger creation.
ILazyModuleRefService
Interface for services using the lazy ModuleRef pattern.
interface ILazyModuleRefService {
Module: ModuleRef;
}TLazyGetter
Function type for a getter that returns a required dependency.
type TLazyGetter<T> = () => T;TOptionalLazyGetter
Function type for a getter that returns a dependency or undefined.
type TOptionalLazyGetter<T> = () => T | undefined;IErrorCategory
Error classification result from ErrorCategorizerService.CategorizeError.
interface IErrorCategory {
type: 'transient' | 'permanent';
retryable: boolean;
strategy: 'retry' | 'fail' | 'backoff';
backoffMs?: number;
}IHealthCheck
Health check response structure.
interface IHealthCheck {
status: string;
timestamp: string;
service?: string;
version?: string;
checks?: Record<string, string>;
}Conditional Exports
// Main exports (all major classes and utilities)
import { AppLogger, CSRFService } from '@pawells/nestjs-shared';
// Common-only exports (lower-level utilities)
import { CSRFService } from '@pawells/nestjs-shared/common';
// Lazy loader types only
import { TLazyGetter } from '@pawells/nestjs-shared/common/utils/lazy-getter.types';
// Testing helpers (AppLoggerMock, SharedTestingModule, etc.)
import { AppLoggerMock } from '@pawells/nestjs-shared/testing';Configuration
Environment Variables
- NODE_ENV: development, production, etc. (affects error details, logging)
- LOG_LEVEL: debug, info, warn, error, fatal, silent (default: info)
- PORT: Server port (default: 3000)
- CSRF_SECRET: Required for CSRF protection (min 32 chars, high entropy)
- METRICS_ENABLED: Enable metrics (default: true)
- SERVICE_NAME: Service name for logging (default: unknown-service)
CSRF_SECRET Generation
# Generate secure CSRF_SECRET
openssl rand -hex 32Security Defaults
- Token Blacklist (when implemented): Fails closed — treats unavailable cache as blacklist
- CSRF Token Generation: Per-IP rate limited (10 per 60s)
- CSRF Validation: Signed tokens with session/IP binding
- Error Sanitization: Stack traces only in development; sensitive fields redacted
- CORS: Implemented via security bootstrap (if configured)
- HTTP Client: Strict SSL/TLS certificate validation (rejectUnauthorized: true)
- Metrics: Dynamic path normalization prevents unbounded cardinality
- Logging: Automatic redaction of passwords, tokens, API keys, emails, IPs
Examples
Complete Application Setup
import { NestFactory } from '@nestjs/core';
import { ConfigModule, CommonModule, MetricsModule } from '@pawells/nestjs-shared';
@Module({
imports: [
ConfigModule, // MUST be first
CommonModule, // Depends on ConfigModule
MetricsModule.ForRoot(), // Optional: Prometheus metrics
// ... feature modules
],
})
export class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// All filters, interceptors, pipes already registered globally
const port = process.env['PORT'] ?? 3000;
await app.listen(port);
}
bootstrap();Custom Error Handling
import { BaseApplicationError, ErrorCategorizerService } from '@pawells/nestjs-shared';
export class InsufficientFundsError extends BaseApplicationError {
constructor(required: number, available: number) {
super(`Insufficient funds: need ${required}, have ${available}`, {
code: 'INSUFFICIENT_FUNDS',
statusCode: 402,
context: { required, available }
});
}
}
// In service
@Catch(InsufficientFundsError)
export class PaymentService {
constructor(private errorCategorizer: ErrorCategorizerService) {}
async processPayment(amount: number) {
if (amount > balance) {
const error = new InsufficientFundsError(amount, balance);
// Automatically caught by GlobalExceptionFilter
// Categorized by ErrorCategorizerService (permanent/fail)
// Sanitized by ErrorSanitizerService
throw error;
}
}
}Related Packages
- @pawells/nestjs-auth - Keycloak integration: token validation, guards, decorators, Admin API client
- @pawells/nestjs-open-telemetry - OpenTelemetry tracing
- @pawells/nestjs-prometheus - Prometheus metrics exporter
License
MIT
