epago-backend-commons
v1.2.7
Published
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
Keywords
Readme
Backend Commons
Librería de utilidades y módulos comunes para servicios basados en NestJS desplegados en Google Cloud Functions (GCF)
Backend Commons es una librería completa que proporciona módulos y utilidades esenciales para construir aplicaciones NestJS robustas y escalables en Google Cloud Platform. Incluye logging estructurado compatible con Google Cloud Logging, contexto por request, configuración tipada, validación, filtros de excepciones, interceptores, rate limiting, error reporting y utilidades para Google Cloud Functions.
🚀 Características
📋 Core Features
- 🔧 Configuración Tipada: Acceso seguro y tipado a variables de entorno
- 📝 Logging Estructurado: Pino con formato GCP y soporte para colores en desarrollo
- 🔄 Request Context: Contexto por invocación con AsyncLocalStorage
- 🛡️ Rate Limiting: Protección contra ataques de fuerza bruta y abuso de API
- 🚨 Error Reporting: Integración con Google Error Reporting
- ✅ Validación: Pipes de validación optimizados para DTOs
- 🎯 Interceptors: Logging y contexto automático para requests
- 🚦 Filtros: Manejo consistente de excepciones HTTP
☁️ Google Cloud Integration
- GCF Adapters: Handlers listos para HTTP y Background Functions
- Cloud Logging: Formato nativo compatible con GCP
- Trace Correlation: Integración automática con Cloud Trace
- Error Reporting: Debugging mejorado con Google Error Reporting
📦 Instalación
Desde npm (público)
npm install epago-backend-commonsDesarrollo Local
Para desarrollo sin publicar:
# Opción A: npm link (cambios en tiempo real)
cd backend-commons
npm link
cd ../mi-proyecto
npm link epago-backend-commons
# Opción B: desde tarball local
cd backend-commons
npm pack
cd ../mi-proyecto
npm install ../backend-commons/epago-backend-commons-1.0.2.tgzNota: Para deslinkear: npm unlink epago-backend-commons en el consumidor, npm unlink en la librería.
🛠️ Configuración Inicial
Requisitos del Sistema
- Node.js: 18+ (recomendado)
- NestJS: ^11.0.0
- Variables de entorno opcionales:
LOG_LEVEL,GCP_PROJECT,SERVICE_NAME
Configuración Básica
// app.module.ts
import { Module } from '@nestjs/common';
import {
AppConfigModule,
LoggerModule,
RequestContextModule,
ErrorReportingModule,
RateLimitingModule,
} from 'epago-backend-commons';
@Module({
imports: [
AppConfigModule,
LoggerModule,
RequestContextModule,
ErrorReportingModule,
RateLimitingModule,
],
})
export class AppModule {}// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {
ContextInterceptor,
LoggingInterceptor,
HttpExceptionFilter,
LoggerService,
} from 'epago-backend-commons';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
// Configurar logger
const logger = app.get(LoggerService);
app.useLogger(logger);
// Configurar interceptors y filtros globales
app.useGlobalInterceptors(
app.get(ContextInterceptor),
app.get(LoggingInterceptor)
);
app.useGlobalFilters(app.get(HttpExceptionFilter));
await app.init();
}
bootstrap();📝 Logging
Configuración Automática
El logger incluye soporte automático para salida coloreada en desarrollo:
import { LoggerService } from 'epago-backend-commons';
@Injectable()
export class UserService {
constructor(private readonly logger: LoggerService) {}
async createUser(userData: CreateUserDto) {
this.logger.log({
msg: 'user:created',
userId: userData.id,
email: userData.email
});
this.logger.warn({
msg: 'rate:limited',
userId: userData.id,
limit: 100
});
this.logger.error({
msg: 'db:error',
operation: 'saveUser',
error: error.message
});
}
}Variables de Entorno
# Configuración de logging
LOG_LEVEL=info # debug, info, warn, error
SERVICE_NAME=my-service # Nombre del servicio
GCP_PROJECT=my-gcp-project # ID del proyecto GCP
NODE_ENV=development # development, staging, production⚙️ Configuración
Configuración Tipada
import { AppConfigService } from 'epago-backend-commons';
@Injectable()
export class MyService {
constructor(private readonly config: AppConfigService) {}
async someMethod() {
const { nodeEnv, serviceName, logLevel } = this.config.getAll();
// Acceso tipado a configuraciones
const isProduction = this.config.isProduction();
const isDevelopment = this.config.isDevelopment();
}
}Validación de Configuración
// config-validation.ts
import { registerAs } from '@nestjs/config';
import { IsString, IsOptional, IsIn } from 'class-validator';
export const appConfig = registerAs('app', () => ({
nodeEnv: process.env.NODE_ENV || 'development',
serviceName: process.env.SERVICE_NAME || 'unknown',
logLevel: process.env.LOG_LEVEL || 'info',
port: parseInt(process.env.PORT, 10) || 3000,
}));
export class AppConfigValidation {
@IsIn(['development', 'staging', 'production'])
nodeEnv: string;
@IsString()
serviceName: string;
@IsIn(['debug', 'info', 'warn', 'error'])
@IsOptional()
logLevel?: string;
}🛡️ Rate Limiting
Configuración Básica
import { RateLimitingModule, RateLimitingService } from 'epago-backend-commons';
@Module({
imports: [RateLimitingModule],
})
export class AppModule {}
// En tu servicio
@Injectable()
export class AuthService {
constructor(private readonly rateLimiting: RateLimitingService) {}
async login(credentials: LoginDto) {
const key = `login:${credentials.email}`;
const isAllowed = await this.rateLimiting.checkLimit(key, {
maxRequests: 5,
windowMs: 15 * 60 * 1000, // 15 minutos
});
if (!isAllowed) {
throw new AppError('RATE_LIMIT_EXCEEDED', 'Too many login attempts');
}
}
}Decorador para Controllers
import { RateLimit } from 'epago-backend-commons';
@Controller('auth')
export class AuthController {
@Post('login')
@RateLimit({ maxRequests: 5, windowMs: 15 * 60 * 1000 })
async login(@Body() credentials: LoginDto) {
// Tu lógica de login
}
}🚨 Error Reporting
Configuración
import { ErrorReportingModule, ErrorReportingService } from 'epago-backend-commons';
@Module({
imports: [ErrorReportingModule],
})
export class AppModule {}
// En tu servicio
@Injectable()
export class PaymentService {
constructor(private readonly errorReporting: ErrorReportingService) {}
async processPayment(paymentData: PaymentDto) {
try {
// Lógica de pago
} catch (error) {
await this.errorReporting.reportError(error, {
context: 'PaymentService.processPayment',
userId: paymentData.userId,
paymentId: paymentData.id,
});
throw error;
}
}
}☁️ Google Cloud Functions
HTTP Functions
// gcf.ts
import { createGcfHttpHandler } from 'epago-backend-commons';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
let app: INestApplication;
async function bootstrap() {
if (!app) {
app = await NestFactory.create(AppModule);
await app.init();
}
return app;
}
export const httpHandler = createGcfHttpHandler(bootstrap);// index.ts (GCF)
const { httpHandler } = require('./gcf');
exports.myHttpFunction = httpHandler;Background Functions
// gcf-background.ts
import { createGcfBackgroundHandler } from 'epago-backend-commons';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
let app: INestApplication;
async function bootstrap() {
if (!app) {
app = await NestFactory.create(AppModule);
await app.init();
}
return app;
}
export const backgroundHandler = createGcfBackgroundHandler(
bootstrap,
async (event) => {
const service = app.get(MyService);
await service.processEvent(event.data);
}
);🔧 Utilidades
Retry con Backoff
import { retry } from 'epago-backend-commons';
const { result, error, attempts } = await retry(
() => callExternalAPI(),
{
retries: 3,
delayMs: 200,
backoff: 'exponential'
}
);
if (error) {
console.log(`Failed after ${attempts} attempts:`, error);
} else {
console.log('Success:', result);
}Manejo de Errores
import { AppError, ERROR_CODES } from 'epago-backend-commons';
// Definir códigos de error
export const USER_ERROR_CODES = {
USER_NOT_FOUND: 'USER_NOT_FOUND',
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
ACCOUNT_LOCKED: 'ACCOUNT_LOCKED',
} as const;
// Lanzar errores tipados
throw new AppError(
USER_ERROR_CODES.USER_NOT_FOUND,
'User with specified ID not found',
{ userId: '123' }
);📚 API Reference
Módulos Exportados
| Módulo | Descripción |
|--------|-------------|
| AppConfigModule | Configuración tipada de la aplicación |
| LoggerModule | Logging estructurado para GCP |
| RequestContextModule | Contexto por request |
| ErrorReportingModule | Integración con Google Error Reporting |
| RateLimitingModule | Protección contra rate limiting |
Servicios Exportados
| Servicio | Descripción |
|----------|-------------|
| AppConfigService | Acceso a configuración tipada |
| LoggerService | Logger estructurado |
| RequestContextService | Gestión de contexto por request |
| ErrorReportingService | Reporte de errores a GCP |
| RateLimitingService | Control de rate limiting |
Interceptors y Filtros
| Clase | Descripción |
|-------|-------------|
| ContextInterceptor | Inyecta contexto en requests |
| LoggingInterceptor | Logging automático de requests |
| HttpExceptionFilter | Manejo consistente de errores HTTP |
| RateLimitingInterceptor | Rate limiting automático |
| RateLimitingGuard | Guard para rate limiting |
Utilidades
| Utilidad | Descripción |
|----------|-------------|
| ValidationPipe | Validación de DTOs |
| AppError | Errores tipados de aplicación |
| retry | Utilidad de reintentos |
| createGcfHttpHandler | Handler para GCF HTTP |
| createGcfBackgroundHandler | Handler para GCF Background |
🧪 Testing
Ejecutar Tests
# Tests unitarios
npm test
# Tests en modo watch
npm run test:watch
# Verificar tipos
npm run typecheck
# Linting
npm run lintMock Logger para Tests
import { MockLoggerService } from 'epago-backend-commons';
@Module({
providers: [
{
provide: LoggerService,
useClass: MockLoggerService,
},
],
})
export class TestModule {}📖 Ejemplos
Aplicación de Ejemplo
La carpeta example/ incluye una aplicación NestJS completa que demuestra todos los módulos:
# Instalar dependencias
npm install
# Ejecutar ejemplo
npx ts-node example/src/main.tsEjemplos por Funcionalidad
Logging Estructurado
// Log con contexto
this.logger.log({
msg: 'user:created',
userId: user.id,
email: user.email,
metadata: {
source: 'api',
version: '1.0.0'
}
});Rate Limiting Avanzado
// Rate limiting por IP y usuario
@Post('api/endpoint')
@RateLimit({
maxRequests: 100,
windowMs: 60 * 1000,
keyGenerator: (req) => `${req.ip}:${req.user?.id || 'anonymous'}`
})
async endpoint() {
// Tu lógica
}Error Reporting con Contexto
await this.errorReporting.reportError(error, {
context: 'PaymentService.processPayment',
severity: 'ERROR',
user: {
id: userId,
email: userEmail
},
request: {
id: requestId,
method: 'POST',
url: '/api/payments'
}
});🏗️ Desarrollo
Scripts Disponibles
# Construir el proyecto
npm run build
# Limpiar dist
npm run clean
# Verificar tipos
npm run typecheck
# Linting
npm run lint
# Tests
npm test
# Tests en modo watch
npm run test:watchEstructura del Proyecto
src/
├── config/ # Configuración tipada
├── context/ # Request context
├── error-reporting/ # Google Error Reporting
├── errors/ # Errores tipados
├── filters/ # Filtros de excepciones
├── gcf/ # Google Cloud Functions adapters
├── interceptors/ # Interceptors
├── logger/ # Logging estructurado
├── rate-limiting/ # Rate limiting
├── utils/ # Utilidades
└── validation/ # Validación🗺️ Roadmap
Próximas Funcionalidades
- [ ] Validación Avanzada: Esquemas de configuración y Secret Manager
- [ ] Redacción de PII: Configuración de redacción en logs
- [ ] Interceptores Adicionales: Timeout y transform
- [ ] Pub/Sub: Publisher/subscriber tipado con idempotencia
- [ ] Tracing: Propagación de trace/span
- [ ] Health Checks: Para dependencias externas
- [ ] DTOs Base: UUID, enteros, fechas, paginación
- [ ] Métricas: Prometheus para monitoreo
- [ ] Circuit Breaker: Para llamadas externas
- [ ] Caching: Redis/Memory para optimización
Contribuir
- Fork el repositorio
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
