Problem Statement
The current backend codebase relies on a monolithic index.js file (~1,500+ lines) that combines multiple distinct responsibilities: Express app configuration, database bootstrap, Kafka producer/consumer initialization, inline route handlers, WebSocket logic, and HTTP server listener. Additionally, server.js is marked as a deprecated file, and routing endpoints across routes/ lack a standardized versioning convention (v1/v2).
This monolithic pattern causes several key maintenance & reliability issues:
- Testing Friction: Importing
index.js in test suites immediately triggers database connections, Kafka workers, and open HTTP listener handles.
- High Code Complexity: Inline endpoints and business logic inside
index.js blur the boundaries between presentation, routing, and domain services.
- API Versioning Inconsistency: Route files are flatly stored in
routes/, making it difficult to manage breaking changes or introduce /api/v1 and /api/v2 endpoints cleanly.
🎯 Proposed Solution & Architectural Blueprint
1. Separation of Responsibilities (app.js vs server.js)
Split index.js into two clear entry points:
2. Directory Structure & API Routing Versioning (v1 / v2)
Refactor the routes/ and controllers/ directories to support clean versioning:
src/ (or root)
├── app.js # Express application setup & middleware stack
├── server.js # Main entry point & infrastructure bootstrapper
├── config/ # Environment & infrastructure configuration
│ ├── database.js
│ ├── firebase.js
│ ├── kafka.js
│ └── secrets.js # Secret loader & validator
├── routes/ # Centralized API Routing
│ ├── index.js # Root router aggregator (/api/v1, /api/v2)
│ ├── v1/ # Version 1 API Routes
│ │ ├── index.js # Aggregator for v1 routes
│ │ ├── authRoutes.js
│ │ ├── userRoutes.js
│ │ ├── articleRoutes.js
│ │ ├── podcastRoutes.js
│ │ └── wellnessRoutes.js
│ └── v2/ # Version 2 API Routes
│ ├── index.js # Aggregator for v2 routes
│ ├── userRoutes.js
│ └── articleRoutes.js
├── controllers/ # Controller Presentation Layer
│ ├── v1/ # Controllers for v1 routes
│ ├── v2/ # Controllers for v2 routes
│ └── admin/ # Admin specific controllers
├── services/ # Business & Domain Logic Layer
│ ├── mqueue/ # Kafka producers and consumer workers
│ └── security/ # Token management & authentication logic
├── middleware/ # Express Middlewares
│ ├── errorHandler.js
│ ├── ratelimit.js
│ └── validator.js
├── loaders/ # Infrastructure loaders (DB, Kafka, Socket)
│ ├── dbLoader.js
│ ├── kafkaLoader.js
│ └── socketLoader.js
└── utils/ # Helpers & Redaction utilities
3. Modular Router Aggregation Pattern
Instead of individually requiring 20+ route files inside index.js, use a nested router aggregator pattern:
routes/index.js
const express = require('express');
const v1Router = require('./v1');
const v2Router = require('./v2');
const router = express.Router();
router.use('/v1', v1Router);
router.use('/v2', v2Router);
module.exports = router;
routes/v1/index.js
const express = require('express');
const userRoutes = require('./userRoutes');
const articleRoutes = require('./articleRoutes');
const podcastRoutes = require('./podcastRoutes');
const v1Router = express.Router();
v1Router.use('/users', userRoutes);
v1Router.use('/articles', articleRoutes);
v1Router.use('/podcasts', podcastRoutes);
module.exports = v1Router;
Mounting in app.js
const apiRouter = require('./routes');
app.use('/api', apiRouter); // Routes accessible via /api/v1/... and /api/v2/...
4. Background Loader Modularization (loaders/)
Move Kafka consumers, producers, and database connections out of index.js into dedicated loader modules:
loaders/dbLoader.js: Database connection lifecycle.
loaders/kafkaLoader.js: Kafka topics initialization and consumer listeners setup.
loaders/socketLoader.js: Socket.io server connection and event handlers.
5. Graceful Shutdown Implementation
In server.js, handle process termination signals cleanly:
const gracefulShutdown = async (signal) => {
console.log(`Received ${signal}. Initiating graceful shutdown...`);
server.close(() => {
console.log('HTTP server closed.');
});
try {
await db.disconnect();
await kafkaProducer.disconnect();
console.log('Database and messaging queue connections closed.');
process.exit(0);
} catch (err) {
console.error('Error during graceful shutdown:', err);
process.exit(1);
}
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
Action Items / Tasks
Benefits
- Clean Architecture & Separation of Concerns: Decouples application logic from server startup and infrastructure listeners.
- Improved Testability: Test suites can import
app.js and use supertest without open handles or port binding conflicts.
- Scalable API Versioning: Clear separation between
/api/v1 and /api/v2 endpoints.
- Maintainable Codebase: Solves the 1,500+ line monolithic
index.js anti-pattern.
Problem Statement
The current backend codebase relies on a monolithic
index.jsfile (~1,500+ lines) that combines multiple distinct responsibilities: Express app configuration, database bootstrap, Kafka producer/consumer initialization, inline route handlers, WebSocket logic, and HTTP server listener. Additionally,server.jsis marked as a deprecated file, and routing endpoints acrossroutes/lack a standardized versioning convention (v1/v2).This monolithic pattern causes several key maintenance & reliability issues:
index.jsin test suites immediately triggers database connections, Kafka workers, and open HTTP listener handles.index.jsblur the boundaries between presentation, routing, and domain services.routes/, making it difficult to manage breaking changes or introduce/api/v1and/api/v2endpoints cleanly.🎯 Proposed Solution & Architectural Blueprint
1. Separation of Responsibilities (
app.jsvsserver.js)Split
index.jsinto two clear entry points:app.js(Express App Factory / Builder)/api/v1,/api/v2,/api/health).errorHandler).appinstance without starting HTTP listener or connecting to DB/Kafka.server.js(Infrastructure Bootstrapper & Entry Point)config/secrets.js).dbConnect), messaging queues (Kafka producers/consumers), and background workers.PORT.SIGTERM,SIGINT).2. Directory Structure & API Routing Versioning (
v1/v2)Refactor the
routes/andcontrollers/directories to support clean versioning:3. Modular Router Aggregation Pattern
Instead of individually requiring 20+ route files inside
index.js, use a nested router aggregator pattern:routes/index.jsroutes/v1/index.jsMounting in
app.js4. Background Loader Modularization (
loaders/)Move Kafka consumers, producers, and database connections out of
index.jsinto dedicated loader modules:loaders/dbLoader.js: Database connection lifecycle.loaders/kafkaLoader.js: Kafka topics initialization and consumer listeners setup.loaders/socketLoader.js: Socket.io server connection and event handlers.5. Graceful Shutdown Implementation
In
server.js, handle process termination signals cleanly:Action Items / Tasks
app.jsand extract Express middleware setup, route mounting, and error handler fromindex.js.server.jsto serve as the main server bootstrap script (DB, Kafka, HTTP listener).index.jsand move them into appropriate controller/service files.routes/intoroutes/v1/androutes/v2/with central router index files.controllers/intov1/andv2/subdirectories to align with API routes.loaders/for Database, Kafka, and Socket.io setup.SIGTERM/SIGINT).app.jsinstead of starting a live HTTP server.Benefits
app.jsand usesupertestwithout open handles or port binding conflicts./api/v1and/api/v2endpoints.index.jsanti-pattern.