@stefaninigo/core
v1.2.0
Published
Enterprise-grade NestJS library for the StefaniniGo microservice ecosystem
Readme
stefaninigo
Enterprise-grade NestJS library for the StefaniniGo microservice ecosystem. Provides database access, file storage, caching, messaging, real-time transport, event-driven architecture, geolocation utilities, health checks, and standardized request/response handling -- all through a unified Strategy + DynamicModule pattern.
Design Philosophy
- Transversal -- shared foundation for all microservices in the ecosystem.
- Generic -- no business logic; only infrastructure concerns.
- Provider-agnostic -- every infrastructure module defines a service interface; concrete implementations (strategies) are swappable without touching consumer code.
- Easy integration --
forRootAsync()one-liner registration,@Inject('token')consumption.
Installation
npm install stefaninigoQuick Start
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import {
DatabaseModule,
StorageModule,
CacheModule,
EventBusModule,
HealthModule,
TraceIdMiddleware,
RequestContextMiddleware,
LoggingInterceptor,
ResponseInterceptor,
ProblemDetailsInterceptor,
} from 'stefaninigo';
import { APP_INTERCEPTOR } from '@nestjs/core';
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot({ load: [configuration], isGlobal: true }),
// Database -- MongoDB provider with injection token 'db'
DatabaseModule.forRootAsync([
{ name: 'db', provider: DatabaseModule.PROVIDERS.MONGODB },
]),
// Storage -- S3 provider with injection token 'storage'
StorageModule.forRootAsync([
{ name: 'storage', provider: StorageModule.PROVIDERS.S3 },
]),
// Cache -- Redis provider with injection token 'cache'
CacheModule.forRootAsync([
{ name: 'cache', provider: CacheModule.PROVIDERS.REDIS },
]),
// Event bus -- global module, no forRoot needed
EventBusModule,
// Health checks
HealthModule.forRoot({
config: {
serviceName: 'my-service',
version: '1.0.0',
database: {
token: 'db',
collection: 'health',
timeoutMs: 3000,
},
},
}),
],
providers: [
// Interceptor chain (order matters -- first registered runs outermost)
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
{ provide: APP_INTERCEPTOR, useClass: ResponseInterceptor },
{ provide: APP_INTERCEPTOR, useClass: ProblemDetailsInterceptor },
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(TraceIdMiddleware, RequestContextMiddleware)
.forRoutes('*');
}
}// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {
createStandardValidationPipe,
setupSwagger,
setupCors,
} from 'stefaninigo';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(createStandardValidationPipe());
app.setGlobalPrefix('api/v1/my-service');
setupCors(app);
setupSwagger(app, {
title: 'My Service',
description: 'API docs',
version: '1.0.0',
});
await app.listen(3000);
}
bootstrap();Modules
| Module | Pattern | Providers | Description |
|--------|---------|-----------|-------------|
| DatabaseModule | Strategy + DynamicModule | MongoDB, DynamoDB, PostgreSQL | Provider-agnostic data access layer |
| StorageModule | Strategy + DynamicModule | S3 | File upload, download, presigned URLs |
| CacheModule | Strategy + DynamicModule | Redis, Local (in-memory) | Key-value caching with TTL |
| MessagingModule | Strategy + DynamicModule | SNS, SQS | Pub/sub and point-to-point messaging |
| TransportModule | Strategy + DynamicModule | AWS API Gateway WebSocket | Real-time bidirectional communication |
| EventBusModule | @Global module | log, SNS, SQS | Toggleable domain event publishing |
| HealthModule | DynamicModule | -- | K8s liveness, readiness, detailed health |
| Core | Interceptors + Middleware | -- | Tracing, logging, RFC 7807 errors, response format |
| Config | Service | -- | Centralized config resolver for all modules |
| Geo | Pure utilities | -- | GeoJSON types, Haversine distance, bounding boxes |
Architecture
Strategy Pattern
Every infrastructure module (DatabaseModule, StorageModule, CacheModule, MessagingModule, TransportModule) follows the same pattern:
- A service interface defines the contract (e.g.
DatabaseService,StorageService,CacheServiceInterface). - Concrete strategies implement that interface per provider (e.g.
MongoDBService,DynamoDBService,PostgreService). - The module exposes
forRootAsync(providers[])which takes an array of{ name, provider }configs. - Each entry creates a named NestJS provider you inject with
@Inject('name').
// Register two databases with different tokens
DatabaseModule.forRootAsync([
{ name: 'mainDb', provider: DatabaseModule.PROVIDERS.MONGODB },
{ name: 'analyticsDb', provider: DatabaseModule.PROVIDERS.MONGODB, configKey: 'analytics' },
{ name: 'dynamoDb', provider: DatabaseModule.PROVIDERS.DYNAMODB },
])
// Inject in a service
constructor(@Inject('mainDb') private readonly db: DatabaseService) {}Interceptor Chain
Interceptors execute in registration order on the request path and in reverse on the response/error path:
Request Flow:
Client --> LoggingInterceptor --> ResponseInterceptor --> ProblemDetailsInterceptor --> Controller
Response Flow (success):
Controller --> ProblemDetailsInterceptor (pass-through) --> ResponseInterceptor (wraps: {success, message}) --> LoggingInterceptor (logs duration) --> Client
Error Flow:
Controller throws --> ProblemDetailsInterceptor (converts to RFC 7807) --> ResponseInterceptor (pass-through if ProblemDetails) --> LoggingInterceptor (logs error) --> Client| Interceptor | Responsibility |
|-------------|---------------|
| LoggingInterceptor | Logs request entry/exit, duration, errors. Includes traceId, controller, handler. Emits structured metrics. |
| ResponseInterceptor | Wraps successful responses as { success: true, message: data }. Wraps non-ProblemDetails errors as { success: false, error, message, statusCode }. |
| ProblemDetailsInterceptor | Converts all errors to RFC 7807 ProblemDetails format. Sanitizes sensitive body fields. Adds debugInfo in development mode. |
Distributed Tracing
Full request lifecycle tracing through AsyncLocalStorage:
Incoming Request
|
v
TraceIdMiddleware -- reads/generates X-Trace-ID, attaches to req & res header
|
v
RequestContextMiddleware -- creates RequestContext in AsyncLocalStorage (traceId, userId, language, startTime)
|
v
TracedLogger -- extends NestJS Logger, auto-prefixes [traceId] to every log line
|
v
LoggingInterceptor -- reads traceId for structured logging
|
v
ProblemDetailsInterceptor -- embeds traceId in error responses// Use TracedLogger in any service for automatic traceId injection
import { TracedLogger } from 'stefaninigo';
export class TicketService {
private readonly logger = new TracedLogger(TicketService.name);
findOne(id: string) {
this.logger.log(`Finding ticket ${id}`);
// Output: [a1b2c3d4-...] Finding ticket TK-001
}
}
// Or access context directly
import { getRequestContext, getTraceId } from 'stefaninigo';
const ctx = getRequestContext(); // { traceId, userId, language, startTime }
const traceId = getTraceId(); // shortcutEvent-Driven Architecture
EventBusModule is a @Global module with 3 publishing strategies, toggled via environment variables:
EventBusService.publish(event)
|
|-- strategy = 'log' --> Logger output (default, zero infra)
|-- strategy = 'sns' --> AWS SNS topic (fan-out)
|-- strategy = 'sqs' --> AWS SQS queue (point-to-point)import { EventBusService } from 'stefaninigo';
import { randomUUID } from 'crypto';
@Injectable()
export class TicketService {
constructor(private readonly eventBus: EventBusService) {}
async create(dto: CreateTicketDto) {
const ticket = await this.repo.create(dto);
await this.eventBus.publish({
eventId: randomUUID(),
eventType: 'ticket.created',
timestamp: new Date().toISOString(),
providerId: dto.providerId,
source: 'tickets-service',
data: { ticketId: ticket.id, clientId: dto.clientId },
});
return ticket;
}
}The library provides typed domain event interfaces for: tickets, routes, geofence, technicians, inventory, clients, locations, contacts, categories, and config. See events/types/domain-events.ts.
Environment Variables
Database
| Variable | Provider | Description |
|----------|----------|-------------|
| database.mongodb.uri | MongoDB | Connection URI |
| database.mongodb.dbName | MongoDB | Default database name |
| database.mongodb.updateAllFields | MongoDB | Replace vs merge on update |
| database.mongodb.returnUpdatedDocument | MongoDB | Return doc after update |
| database.dynamodb.region | DynamoDB | AWS region |
| database.dynamodb.accessKeyId | DynamoDB | AWS access key |
| database.dynamodb.secretAccessKey | DynamoDB | AWS secret key |
| database.postgres.host | PostgreSQL | Host |
| database.postgres.port | PostgreSQL | Port |
| database.postgres.username | PostgreSQL | Username |
| database.postgres.password | PostgreSQL | Password |
| database.postgres.database | PostgreSQL | Database name |
Storage
| Variable | Provider | Description |
|----------|----------|-------------|
| storage.s3.region | S3 | AWS region |
| storage.s3.accessKeyId | S3 | AWS access key |
| storage.s3.secretAccessKey | S3 | AWS secret key |
| storage.s3.bucket | S3 | Bucket name |
| storage.s3.containerPath | S3 | Default key prefix |
| storage.s3.expiresIn | S3 | Presigned URL expiry in seconds (default: 3600) |
Cache
| Variable | Provider | Description |
|----------|----------|-------------|
| cache.redis.host | Redis | Redis host |
| cache.redis.port | Redis | Redis port |
Messaging
| Variable | Provider | Description |
|----------|----------|-------------|
| message.sns.region | SNS | AWS region |
| message.sns.accessKeyId | SNS | AWS access key |
| message.sns.secretAccessKey | SNS | AWS secret key |
| message.sqs.region | SQS | AWS region |
| message.sqs.accessKeyId | SQS | AWS access key |
| message.sqs.secretAccessKey | SQS | AWS secret key |
| message.sqs.queueUrl | SQS | Default queue URL |
| message.sqs.MaxNumberOfMessages | SQS | Max messages per poll (default: 10) |
| message.sqs.WaitTimeSeconds | SQS | Long poll wait (default: 20) |
| message.sqs.pollInterval | SQS | Poll interval ms (default: 10000) |
Transport
| Variable | Provider | Description |
|----------|----------|-------------|
| transport.aws_ws.domain | AWS WebSocket | API Gateway WebSocket endpoint |
EventBus
| Variable | Default | Description |
|----------|---------|-------------|
| EVENTS_ENABLED | false | Enable/disable event publishing |
| EVENTS_STRATEGY | log | Strategy: log, sns, or sqs |
| EVENTS_SNS_TOPIC_ARN | -- | SNS topic ARN (when strategy=sns) |
| EVENTS_SQS_QUEUE_URL | -- | SQS queue URL (when strategy=sqs) |
Core
| Variable | Default | Description |
|----------|---------|-------------|
| NODE_ENV | development | Controls debug info in error responses |
| CORS_ORIGINS | * | Comma-separated allowed origins |
Module Documentation
Each module has its own ARCHITECTURE.md with detailed API reference, usage examples, and configuration:
- Core -- Interceptors, Middleware, Context, Logger, Errors, Health
- Database -- MongoDB, DynamoDB, PostgreSQL
- Storage -- S3 file operations
- Cache -- Redis and Local providers
- Messaging -- AWS SNS and SQS
- Events -- Domain event bus
- Transport -- WebSocket and HTTP
- Config -- Centralized configuration resolver
- Geo -- GeoJSON, distance, bounding boxes
Tech Stack
| Dependency | Version | Purpose | |------------|---------|---------| | NestJS | 11.x | Framework | | TypeScript | 5.x | Language | | MongoDB driver | 6.x | MongoDB provider | | pg | 8.x | PostgreSQL provider | | AWS SDK v3 | 3.x | S3, SNS, SQS, DynamoDB, API Gateway | | ioredis | 5.x | Redis provider | | axios | 1.x | HTTP transport | | ws | 8.x | WebSocket transport |
License
UNLICENSED -- Internal use only.
