Files
FrenoCorp/apps/api/src/index.ts
Michael Freno 3aead0d7bb Implement Redis rate limiting middleware for spam endpoints (FRE-4507)
- Add ioredis dependency to API package
- Create Redis connection utility (apps/api/src/config/redis.ts)
- Create Redis-backed spam rate limit middleware with per-minute and daily limits
- Create spam classification routes (SMS, number reputation, call analysis, feedback)
- Register middleware and routes in API server
- Add 7 passing tests for rate limit enforcement
- Update vitest config with required env vars

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-29 20:54:39 -04:00

107 lines
2.8 KiB
TypeScript

import Fastify from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import { authMiddleware } from './middleware/auth.middleware';
import { rateLimitMiddleware } from './middleware/rate-limit.middleware';
import { spamRateLimitMiddleware } from './middleware/spam-rate-limit.middleware';
import { errorHandlingMiddleware } from './middleware/error-handling.middleware';
import { loggingMiddleware } from './middleware/logging.middleware';
import { apiEnv, loggingConfig } from './config/api.config';
import { routes } from './routes';
const fastify = Fastify({
logger: loggingConfig,
ignoreTrailingSlash: true,
maxParamLength: 500,
});
// Register plugins
async function registerPlugins() {
// CORS configuration
await fastify.register(cors, {
origin: apiEnv.CORS_ORIGIN,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
credentials: true,
});
// Security headers
await fastify.register(helmet, {
global: true,
contentSecurityPolicy: false,
});
// Rate limiting
await fastify.register(rateLimitMiddleware);
// SpamShield rate limiting (Redis-backed)
await fastify.register(spamRateLimitMiddleware);
// Authentication
await fastify.register(authMiddleware);
// Logging
await fastify.register(loggingMiddleware);
// Error handling
await fastify.register(errorHandlingMiddleware);
}
// Register routes
async function registerRoutes() {
await fastify.register(routes, { prefix: '/api/v1' });
}
// Health check endpoint
fastify.get('/health', async () => {
return { status: 'ok', timestamp: new Date().toISOString() };
});
// Root endpoint
fastify.get('/', async () => {
return {
name: 'FrenoCorp API Gateway',
version: '1.0.0',
environment: apiEnv.NODE_ENV,
};
});
// Start server
async function start() {
await registerPlugins();
await registerRoutes();
try {
await fastify.listen({
port: apiEnv.PORT,
host: apiEnv.HOST,
});
console.log(`🚀 API Gateway running at http://${apiEnv.HOST}:${apiEnv.PORT}`);
console.log(`📝 Environment: ${apiEnv.NODE_ENV}`);
console.log(`📊 Rate limit window: ${apiEnv.API_RATE_LIMIT_WINDOW}ms`);
console.log(`📈 Max requests: ${apiEnv.API_RATE_LIMIT_MAX_REQUESTS}`);
} catch (err) {
console.error(err);
process.exit(1);
}
}
// Graceful shutdown
const gracefulShutdown = async (signal: string) => {
console.log(`\n🛑 ${signal} received, shutting down gracefully...`);
await fastify.close();
console.log('✅ Server closed');
process.exit(0);
};
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
// Export for testing
export { fastify };
// Start if running directly
if (process.argv[1] === new URL(import.meta.url).pathname) {
start();
}