@pagamio/nestjs-api-commons
v0.2.0
Published
Common utilities, modules, and services for NestJS-based Pagamio APIs
Maintainers
Readme
Pagamio NestJS API Commons
A comprehensive commons library for NestJS-based Pagamio APIs, providing reusable modules, utilities, and services.
📦 Installation
pnpm add @pagamio/nestjs-api-commons🏗️ Module Structure
1. Authentication Module (auth/)
Provides authentication and authorization utilities.
Features:
- JWT authentication strategy
- API Key authentication strategy
- Role-based access control
- Custom decorators for public routes and role management
Usage:
import {
AuthModule,
JwtAuthGuard,
Roles,
Public,
CurrentUser,
} from '@pagamio/nestjs-api-commons';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
AuthModule.forRoot(), // ✅ Dynamic module
],
})
export class AppModule {}
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
@Get('profile')
@Roles('admin', 'user')
getProfile(@CurrentUser() user: any) {
return user;
}
@Public()
@Get('public')
getPublicData() {
return { message: 'Public endpoint' };
}
}2. Database Module (database/)
Base entities and repositories with common database operations.
Features:
- Base entity with UUID, timestamps, and soft deletes
- Base repository with common CRUD operations
- Transaction interceptor
Usage:
import { BaseEntity, BaseRepository } from '@pagamio/nestjs-api-commons';
@Entity()
export class User extends BaseEntity {
@Column()
name: string;
@Column()
email: string;
}
@Injectable()
export class UserRepository extends BaseRepository<User> {}3. Validation Module (validation/)
DTOs and pipes for request validation.
Features:
- Pagination DTO
- Base query DTO with search and sorting
- Custom validation pipes
Usage:
import { PaginationDto, BaseQueryDto } from '@pagamio/nestjs-api-commons';
@Controller('products')
export class ProductsController {
@Get()
findAll(@Query() query: PaginationDto & BaseQueryDto) {
return this.productsService.findAll(query);
}
}4. Observability Module (observability/)
Logging, health checks, and metrics.
Features:
- Custom logger service with Winston integration
- Request logging interceptor
- Health check controller
- Metrics service
- Configurable log levels and transports
Usage:
import {
LoggerService,
MetricsService,
ObservabilityModule,
} from '@pagamio/nestjs-api-commons';
@Module({
imports: [ObservabilityModule],
})
export class AppModule {}
@Injectable()
export class MyService {
constructor(
private logger: LoggerService,
private metrics: MetricsService,
) {
this.logger.setContext('MyService');
}
doSomething() {
this.logger.log('Doing something...');
this.metrics.incrementCounter('my_service_calls');
}
}Logger Features:
- Structured logging with Winston
- Multiple transports (console, file)
- Automatic file logging in production
- Colorized console output
- Contextual logging
5. HTTP Module (http/)
HTTP interceptors and exception filters.
Features:
- Response wrapper interceptor
- Timeout interceptor
- Logging interceptor
- HTTP exception filter
- Global exception filter
Usage:
import {
ResponseWrapperInterceptor,
HttpExceptionFilter,
AllExceptionsFilter,
HttpModule,
} from '@pagamio/nestjs-api-commons';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new ResponseWrapperInterceptor());
app.useGlobalFilters(new HttpExceptionFilter(), new AllExceptionsFilter());
await app.listen(3000);
}6. Events Module (events/)
Event-driven architecture infrastructure with base classes, decorators, and utilities.
Features:
- BaseEvent: Abstract base class for all events with metadata support
- System Events: Pre-built events for common system occurrences
- @DomainEvent: Decorator for marking domain events with metadata
- @EventListener: Enhanced event listener decorator with retry logic
- EventSerializer: Utilities for serializing/deserializing events
- EventsModule: Module configuration with EventEmitter2
Usage:
import {
BaseEvent,
DomainEvent,
EventListener,
EventsModule,
SYSTEM_EVENTS,
} from '@pagamio/nestjs-api-commons';
// 1. Import EventsModule in your AppModule
@Module({
imports: [
EventsModule.forRoot({
wildcard: true,
delimiter: '.',
maxListeners: 10,
}),
],
})
export class AppModule {}
// 2. Create domain events by extending BaseEvent
@DomainEvent('user.created', { version: 1 })
export class UserCreatedEvent extends BaseEvent {
constructor(
public readonly userId: string,
public readonly email: string,
) {
super(userId); // Pass userId for user-initiated events
}
getPayload() {
return {
userId: this.userId,
email: this.email,
};
}
}
// 3. Emit events in your services
@Injectable()
export class UsersService {
constructor(private readonly eventEmitter: EventEmitter2) {}
async create(dto: CreateUserDto) {
const user = await this.usersRepository.save(dto);
// Emit event
this.eventEmitter.emit(
'user.created',
new UserCreatedEvent(user.id, user.email),
);
return user;
}
}
// 4. Listen to events with automatic retry
@Injectable()
export class UserEventsListener {
constructor(private readonly logger: LoggerService) {}
@EventListener('user.created', {
async: true,
maxRetries: 3,
retryDelay: 1000,
})
async handleUserCreated(event: UserCreatedEvent) {
this.logger.log(`User created: ${event.email}`);
// Send welcome email, etc.
}
}System Events:
Pre-built events for common system-level occurrences:
import {
AppStartedEvent,
AppShuttingDownEvent,
HealthCheckFailedEvent,
CriticalErrorEvent,
SYSTEM_EVENTS,
} from '@pagamio/nestjs-api-commons';
// Emit system events
this.eventEmitter.emit(
SYSTEM_EVENTS.APP_STARTED,
new AppStartedEvent('MyApp', '1.0.0', 'production', 3000),
);Event Serialization:
import { EventSerializer } from '@pagamio/nestjs-api-commons';
const event = new UserCreatedEvent('user-123', '[email protected]');
// Serialize to JSON string
const json = EventSerializer.serialize(event);
// Deserialize back
const deserialized = EventSerializer.deserialize(json);
// Batch operations
const events = [event1, event2, event3];
const batchJson = EventSerializer.serializeBatch(events);BaseEvent Features:
Every event automatically includes:
eventId: Unique UUID for deduplicationoccurredAt: Timestamp when event occurreduserId: Optional user ID who initiated the eventcorrelationId: Optional correlation ID for distributed tracingmetadata: Optional additional metadata
7. Jobs Module (jobs/)
Background job processing infrastructure with BullMQ integration.
Features:
- BaseProcessor: Abstract base class providing reference implementation patterns (⚠️ cannot be extended across modules due to NestJS BullMQ validation)
- JobResult: Standardized result interface with success/error/metadata
- @RetryableJob: Decorator for configuring retry behavior with backoff strategies
- @JobMetrics: Decorator for automatic metrics collection
- QueueFactory: Standardized queue creation (high-priority, critical, batch)
- Retry Strategies: Exponential, linear, jittered, conditional, and error-specific
- QueueHealthIndicator: Health checks for queue monitoring
- Custom Errors: JobError, JobTimeoutError, JobValidationError, etc.
Usage:
import {
JobsModule,
BaseProcessor,
JobResult,
RetryableJob,
JobMetricsDecorator,
QueueFactory,
} from '@pagamio/nestjs-api-commons';
// 1. Import JobsModule in your AppModule
@Module({
imports: [
JobsModule.forRoot({
connection: {
host: 'localhost',
port: 6379,
},
enableHealthChecks: true,
}),
],
})
export class AppModule {}
// 2. Register queues in feature modules
@Module({
imports: [
JobsModule.forFeature([{ name: 'email' }, { name: 'data-export' }]),
],
})
export class JobsFeatureModule {}
// 3. Create job processors by extending WorkerHost directly
// ⚠️ IMPORTANT: Due to NestJS BullMQ's instanceof validation, processors MUST extend
// WorkerHost directly. BaseProcessor cannot be used as a parent class across module boundaries.
// Instead, implement the process() method and create JobResult objects manually.
@Processor('email')
@RetryableJob({
maxAttempts: 3,
backoffType: 'exponential',
delay: 1000,
maxDelay: 10000,
})
@JobMetricsDecorator({
prefix: 'email_job',
tags: { processor: 'email' },
})
export class EmailProcessor extends WorkerHost {
constructor(protected readonly logger: LoggerService) {
super();
this.logger.setContext(EmailProcessor.name);
}
async process(job: Job<EmailJobDto>): Promise<JobResult<{ sent: boolean }>> {
try {
// Your processing logic
const sent = await this.sendEmail(job.data);
// Return standardized result manually
return {
success: true,
data: { sent },
metadata: { recipient: job.data.to },
};
} catch (error) {
// Handle errors
throw error;
}
}
}
// Note: BaseProcessor provides reference implementation patterns but cannot be
// extended directly. Use its code as a template for common functionality.
// 4. Enqueue jobs in your services
@Injectable()
export class EmailService {
constructor(@InjectQueue('email') private emailQueue: Queue) {}
async sendWelcomeEmail(email: string) {
await this.emailQueue.add('send', {
to: email,
subject: 'Welcome!',
body: 'Thanks for joining us!',
});
}
}Queue Factory:
import { QueueFactory } from '@pagamio/nestjs-api-commons';
const connection = { host: 'localhost', port: 6379 };
// Standard queue
const emailQueue = QueueFactory.createQueue('email', connection);
// High-priority queue (10 priority, 5 attempts, fast backoff)
const urgentQueue = QueueFactory.createHighPriorityQueue('urgent', connection);
// Critical queue (20 priority, 10 attempts, keeps 1000 completed jobs)
const criticalQueue = QueueFactory.createCriticalQueue('critical', connection);
// Batch processing queue (1 attempt, auto-cleanup)
const batchQueue = QueueFactory.createBatchQueue('batch', connection);
// Custom retry configuration
const customQueue = QueueFactory.createQueueWithRetry(
'custom',
connection,
5, // max attempts
2000, // retry delay in ms
);Retry Strategies:
import {
exponentialBackoff,
linearBackoff,
jitteredExponentialBackoff,
conditionalRetry,
withMaxAttempts,
errorSpecificRetry,
} from '@pagamio/nestjs-api-commons';
// Exponential backoff: 1s, 2s, 4s, 8s...
const strategy1 = exponentialBackoff(1000, 60000);
// Linear backoff: 1s, 3s, 5s, 7s...
const strategy2 = linearBackoff(1000, 2000, 30000);
// Jittered exponential (prevents thundering herd)
const strategy3 = jitteredExponentialBackoff(1000, 60000, 0.2);
// Conditional retry (only retry certain errors)
const strategy4 = conditionalRetry(
exponentialBackoff(1000),
(attempt, error) => error.message !== 'Permanent failure',
);
// Max attempts wrapper
const strategy5 = withMaxAttempts(exponentialBackoff(1000), 5);
// Error-specific strategies
const strategy6 = errorSpecificRetry(
new Map([
[NetworkError, exponentialBackoff(1000)],
[ValidationError, fixedDelay(500)],
]),
exponentialBackoff(2000), // default
);Queue Health Checks:
import {
QueueHealthIndicator,
HealthCheck,
HealthCheckService,
} from '@pagamio/nestjs-api-commons';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private queueHealth: QueueHealthIndicator,
@InjectQueue('email') private emailQueue: Queue,
) {}
@Get('queues')
@HealthCheck()
checkQueues() {
return this.health.check([
() =>
this.queueHealth.isHealthy('email-queue', this.emailQueue, {
maxFailedJobs: 50,
maxWaitingJobs: 1000,
}),
() => this.queueHealth.pingCheck('email-ping', this.emailQueue),
]);
}
}Custom Job Errors:
import {
JobError,
JobTimeoutError,
JobValidationError,
JobProcessingError,
JobMaxRetriesError,
} from '@pagamio/nestjs-api-commons';
// In your processor
if (timeout) {
throw new JobTimeoutError('Job exceeded 30s timeout');
}
if (invalid) {
throw new JobValidationError('Invalid email format', {
field: 'email',
value: job.data.email,
});
}
// Errors automatically logged and tracked8. Pagination Module (pagination/)
Comprehensive pagination utilities supporting both offset-based and cursor-based pagination.
Features:
- Offset Pagination: Traditional page/limit pagination with metadata
- Cursor Pagination: High-performance pagination for large datasets
- @Paginate Decorator: Auto-documentation for pagination endpoints
- CursorEncoder: Base64 encoding/decoding for cursor strings
- PaginationHelper: Utilities for creating paginated responses
Usage:
Offset-Based Pagination:
import {
PaginationQueryDto,
PaginatedResponseDto,
PaginationHelper,
Paginate,
} from '@pagamio/nestjs-api-commons';
@Controller('products')
export class ProductsController {
@Get()
@Paginate(20, 100) // default limit 20, max 100
async findAll(
@Query() query: PaginationQueryDto,
): Promise<PaginatedResponseDto<Product>> {
const [items, total] = await this.productsService.findAndCount({
skip: query.getSkip(),
take: query.getTake(),
order: { [query.sortBy || 'createdAt']: query.order },
});
return PaginationHelper.createResponse(items, query, total);
}
}Cursor-Based Pagination:
import {
CursorPaginationQueryDto,
CursorPaginatedResponseDto,
PaginationHelper,
CursorPaginate,
CursorEncoder,
} from '@pagamio/nestjs-api-commons';
@Controller('messages')
export class MessagesController {
@Get()
@CursorPaginate(50, 100)
async findAll(
@Query() query: CursorPaginationQueryDto,
): Promise<CursorPaginatedResponseDto<Message>> {
const limit = query.limit || 50;
// Build query with cursor filter
const where: any = {};
if (query.cursor) {
const filter = PaginationHelper.parseCursorToFilter(
query.cursor,
query.sortBy || 'createdAt',
query.order || 'asc',
);
Object.assign(where, filter);
}
// Fetch one extra item to check for next page
const items = await this.messagesService.find({
where,
take: limit + 1,
order: { [query.sortBy || 'createdAt']: query.order },
});
return PaginationHelper.createCursorResponse(items, query);
}
}Cursor Utilities:
import { CursorEncoder } from '@pagamio/nestjs-api-commons';
// Encode cursor
const cursor = CursorEncoder.encode({
id: '123',
createdAt: '2025-11-18T00:00:00Z',
});
// Decode cursor
const decoded = CursorEncoder.decode(cursor);
// Create cursor from entity
const entity = { id: '123', createdAt: new Date(), name: 'Test' };
const cursor = CursorEncoder.createFromEntity(entity, 'createdAt');
// Validate cursor
if (CursorEncoder.isValid(cursor)) {
// Use cursor
}Response Format:
Offset pagination response:
{
"data": [...],
"meta": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5,
"hasNextPage": true,
"hasPreviousPage": false
}
}Cursor pagination response:
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6IjEyMyJ9...",
"previousCursor": null,
"hasNextPage": true,
"hasPreviousPage": false,
"count": 20
}
}9. Versioning Module (versioning/)
API versioning and deprecation utilities for managing API lifecycle.
Features:
- @Deprecated Decorator: Mark endpoints as deprecated with sunset dates
- DeprecationWarningInterceptor: Automatic deprecation headers (RFC 8594)
- VersionHeaderInterceptor: Add API version information to responses
- VersionParser: Parse and compare semver versions
Usage:
Deprecating Endpoints:
import { Deprecated } from '@pagamio/nestjs-api-commons';
@Controller({ path: 'users', version: '1' })
export class UsersV1Controller {
@Get(':id')
@Deprecated({
message: 'Use /api/v2/users/:id instead for enhanced user data',
sunset: '2026-06-01',
alternativeUrl: '/api/v2/users/:id',
since: '2.0.0',
})
async findOne(@Param('id') id: string) {
// Old implementation
return this.usersService.findOne(id);
}
}Response Headers (automatic):
Deprecation: true
Sunset: Sat, 01 Jun 2026 00:00:00 GMT
Link: </api/v2/users/:id>; rel="alternate"
X-API-Deprecation-Info: {"message":"Use /api/v2/users/:id instead...","since":"2.0.0","sunset":"2026-06-01","alternative":"/api/v2/users/:id"}Version Parsing and Comparison:
import { VersionParser } from '@pagamio/nestjs-api-commons';
// Parse versions
const version = VersionParser.parse('v2.1.0');
// { major: 2, minor: 1, patch: 0 }
// Compare versions
VersionParser.compare('2.0.0', '1.9.9'); // 1 (greater)
VersionParser.isGreaterThan('2.0.0', '1.0.0'); // true
VersionParser.isLessThan('1.0.0', '2.0.0'); // true
VersionParser.isEqual('v1.0', '1.0.0'); // true
// Validate versions
VersionParser.isValid('1.2.3'); // true
VersionParser.isValid('invalid'); // false
// Extract from headers
const version = VersionParser.extractFromHeader(
'application/json; version=2.0',
);
// '2.0'Adding Version Headers:
import { VersionHeaderInterceptor } from '@pagamio/nestjs-api-commons';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useValue: new VersionHeaderInterceptor('1.0.0'),
},
],
})
export class AppModule {}Response Headers:
X-API-Version: 1.0.0
X-API-Requested-Version: v110. Utils (utils/)
Common utility functions.
Common utility functions.
Features:
- CryptoUtil: Password hashing, comparison, random string generation
- DateUtil: Date manipulation and formatting
- StringUtil: String transformations (camelCase, snake_case, slugify, etc.)
Usage:
import { CryptoUtil, DateUtil, StringUtil } from '@pagamio/nestjs-api-commons';
// Crypto utilities
const hashed = await CryptoUtil.hashPassword('password123');
const isValid = await CryptoUtil.comparePassword('password123', hashed);
// Date utilities
const tomorrow = DateUtil.addDays(new Date(), 1);
const isPast = DateUtil.isPast(new Date('2020-01-01'));
// String utilities
const slug = StringUtil.slugify('Hello World!'); // 'hello-world'
const camel = StringUtil.toCamelCase('hello world'); // 'helloWorld'🚀 Quick Start
- Install the package:
pnpm add @pagamio/nestjs-api-commons- Import modules in your app:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; // ← Add this
import {
AuthModule,
DatabaseModule,
ValidationModule,
ObservabilityModule,
HttpModule,
} from '@pagamio/nestjs-api-commons';
@Module({
imports: [
// ← Configure environment variables first
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
AuthModule,
DatabaseModule,
ValidationModule,
ObservabilityModule,
HttpModule,
],
})
export class AppModule {}- Use utilities and decorators:
import {
Public,
Roles,
CurrentUser,
CryptoUtil,
LoggerService,
} from '@pagamio/nestjs-api-commons';💡 Complete Usage Example
Here's a complete example showing how to use the library in your NestJS application:
1. Install dependencies in your project:
pnpm add @pagamio/nestjs-api-commons @nestjs/config2. Create .env file in your project root:
JWT_SECRET=my-super-secret-key-change-in-production
VALID_API_KEYS=dev-key-123,prod-key-456
LOG_LEVEL=debug
NODE_ENV=development3. Configure your app.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule, ObservabilityModule } from '@pagamio/nestjs-api-commons';
import { UsersModule } from './users/users.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
AuthModule,
ObservabilityModule,
UsersModule,
],
})
export class AppModule {}4. Use in your controller:
import { Controller, Get, UseGuards } from '@nestjs/common';
import {
JwtAuthGuard,
RolesGuard,
Roles,
Public,
CurrentUser,
LoggerService,
} from '@pagamio/nestjs-api-commons';
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UsersController {
constructor(private logger: LoggerService) {
this.logger.setContext('UsersController');
}
@Get('profile')
@Roles('admin', 'user')
getProfile(@CurrentUser() user: any) {
this.logger.log(`User ${user.userId} accessed their profile`);
return user;
}
@Public()
@Get('public')
getPublicInfo() {
this.logger.log('Public endpoint accessed');
return { message: 'This is public' };
}
}5. Run your application:
pnpm run start:devThe library will automatically read the environment variables from your .env file!
🔧 Configuration
Environment Variables
This library reads environment variables from your consuming application, not from the library itself. You need to configure these in your main project.
Where to place your .env file:
- Place the
.envfile in the root directory of your application (the project that uses this library) - Not in the
node_modules/@pagamio/nestjs-api-commons/folder
How the library picks up environment variables:
The library uses process.env to read configuration, which means:
Using @nestjs/config (Recommended):
// In your main application's app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AuthModule, ObservabilityModule, } from '@pagamio/nestjs-api-commons'; @Module({ imports: [ // Load .env file from your project root ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env', // or '.env.development', '.env.production', etc. }), AuthModule, ObservabilityModule, // ... other modules ], }) export class AppModule {}Using dotenv directly:
// In your main.ts (before creating the app) import * as dotenv from 'dotenv'; dotenv.config(); import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
Required environment variables in your application's .env file:
# JWT Configuration (for AuthModule)
JWT_SECRET=your-secret-key-here
# API Key Configuration (for API Key authentication)
VALID_API_KEYS=key1,key2,key3
# Logging Configuration (for LoggerService)
LOG_LEVEL=info
# Options: error, warn, info, debug, verbose
NODE_ENV=development
# Use 'production' to enable file logging to logs/ directoryExample project structure:
your-application/
├── .env # ← Your environment variables go here
├── .env.development
├── .env.production
├── src/
│ ├── app.module.ts # ← Configure ConfigModule here
│ ├── main.ts
│ └── ...
├── node_modules/
│ └── @pagamio/nestjs-api-commons/ # ← Library reads from YOUR .env
├── package.json
└── ...Note: The library modules will automatically pick up these environment variables when they are instantiated in your application. You don't need to pass them manually.
🔍 Troubleshooting Environment Variables
Problem: "JWT_SECRET is undefined"
- ✅ Ensure
.envfile is in your project root (not in node_modules) - ✅ Add
ConfigModule.forRoot()before importingAuthModule - ✅ Check that your
.envfile is not in.gitignoreduring development - ✅ Restart your development server after adding/changing
.env
Problem: "Logger not working as expected"
- ✅ Set
LOG_LEVELenvironment variable (defaults to 'info') - ✅ Check
NODE_ENV- file logging only works in production
Problem: "API Key authentication not working"
- ✅ Ensure
VALID_API_KEYSis a comma-separated list with no spaces - ✅ Example:
VALID_API_KEYS=key1,key2,key3✅ - ✅ Wrong:
VALID_API_KEYS=key1, key2, key3❌ (spaces will cause issues)
📝 Development
# Install dependencies
pnpm install
# Build the library
pnpm run build
# Run tests
pnpm test
# Lint
pnpm run lint🤝 Contributing
Contributions are welcome! Please check the CHANGELOG.md for version history.
📄 License
MIT - See LICENSE file for details
👥 Author
Pagamio Team
