Description
The current src/index.js file has grown into a monolithic entry point that handles server creation, middleware registration, route mounting, service initialization, and configuration loading all in a single file. This violates the Single Responsibility Principle and makes the file difficult to read, test, and maintain. This issue refactors index.js into a modular Express application structure following established conventions.
The refactored structure will split index.js into the following modules:
src/app.js: Creates and configures the Express application. Registers middleware in the correct order (security → logging → body parsing → rate limiting → routes → error handling). Does NOT start the server — this makes the app instance importable for testing with supertest without triggering server startup.
src/server.js: Imports app.js, creates the HTTP server (with optional WebSocket attachment from Issue #7), starts listening on the configured port, and handles graceful shutdown (SIGTERM/SIGINT). This is the entry point for production: node src/server.js.
src/config/index.js: Centralized configuration module that loads and validates environment variables at startup. Exports a frozen config object with all settings (port, database URLs, JWT secret, rate limits, etc.). Provides sensible defaults and throws clear errors for missing required variables.
src/routes/index.js: Route aggregator that imports all route modules and mounts them under their respective prefixes. This keeps the route mounting logic organized and allows routes to be enabled/disabled via configuration.
src/services/index.js: Service initializer that creates and connects all services (cache, queue, event listener, WebSocket) in the correct order. Handles graceful shutdown of each service.
This modular structure follows the industry-standard separation of concerns used in production Express applications and makes each component independently testable.
Technical Context & Impact
- Dependencies: No new external dependencies. This is purely a code organization refactoring.
- Architecture: The new file structure adds
app.js, server.js, config/index.js, routes/index.js, and services/index.js. The old index.js is removed. Test files that import the app need to update their import paths.
- Impact: High-impact for developer experience and testing. The ability to
const app = require('./app') in tests without starting the server is a significant improvement for test speed and simplicity. This refactoring also makes the application structure clearer for new team members.
Step-by-Step Implementation Guide
- Create Configuration Module: Write
src/config/index.js. Export a frozen object with all configuration values from environment variables. Use process.env with destructuring and default values. Validate required variables (JWT_SECRET, SOROBAN_RPC_URL, REDIS_URL in production) and throw on startup if missing.
- Create app.js: Write
src/app.js. Import Express, apply middleware in order (helmet, cors, json body parser with size limit, logger, rate limiter, routes from routes/index.js, error handler). Export the configured app instance. Do not start listening.
- Create routes/index.js: Write
src/routes/index.js. Import all route modules (auth, admin, analytics, exports, etc.) and mount them under /api/auth, /api/admin, etc. Export a router or a function that mounts routes on the main app.
- Create services/index.js: Write
src/services/index.js. Export initServices(app) that initializes cache, queue, event listener, WebSocket in dependency order. Export shutdownServices() that gracefully stops all services. Services should be stored in a module-level registry.
- Create server.js: Write
src/server.js. Import app and services/index.js. Create HTTP server from app. Initialize services. Start listening on port from config. Register process.on('SIGTERM'/'SIGINT') handlers that shutdown services and close the server gracefully.
- Update index.js: Replace the old monolithic
src/index.js with a single line: require('./server.js') or keep as the main entry point that requires server.js.
- Update package.json: Ensure
main (or start script) points to src/server.js or src/index.js (which requires server.js). Update test scripts to import from src/app.js instead of src/index.js.
- Update Tests: Refactor test files that previously required
src/index.js to now require src/app.js directly. Create a test helper tests/helpers/app.js that provides a configured app instance for supertest.
- Write Tests: Create
tests/unit/app.test.js that verifies the app is created without errors, middleware is applied in correct order, and error handler is present. Create tests/integration/server.test.js testing server startup and graceful shutdown.
Verification & Testing Steps
- Run
node src/server.js — verify the server starts without errors and logs the expected startup message with port and environment.
- Run
npm test — verify all existing tests pass. Tests should be faster since they import app.js directly without server startup.
- Send a
SIGTERM signal to the running server — verify graceful shutdown: server stops accepting new connections, ongoing requests complete, services shut down, and the process exits cleanly.
- Set a missing required environment variable (e.g., unset
JWT_SECRET) and start the server — verify a clear error message is thrown at startup indicating which variable is missing.
- Import
app.js in a Node REPL or test file — verify it returns an Express application instance without starting the HTTP server.
- Check the final
index.js — verify it's minimal (just requires server.js) and all logic has been migrated to the new modules.
Description
The current
src/index.jsfile has grown into a monolithic entry point that handles server creation, middleware registration, route mounting, service initialization, and configuration loading all in a single file. This violates the Single Responsibility Principle and makes the file difficult to read, test, and maintain. This issue refactorsindex.jsinto a modular Express application structure following established conventions.The refactored structure will split
index.jsinto the following modules:src/app.js: Creates and configures the Express application. Registers middleware in the correct order (security → logging → body parsing → rate limiting → routes → error handling). Does NOT start the server — this makes the app instance importable for testing with supertest without triggering server startup.src/server.js: Importsapp.js, creates the HTTP server (with optional WebSocket attachment from Issue #7), starts listening on the configured port, and handles graceful shutdown (SIGTERM/SIGINT). This is the entry point for production:node src/server.js.src/config/index.js: Centralized configuration module that loads and validates environment variables at startup. Exports a frozen config object with all settings (port, database URLs, JWT secret, rate limits, etc.). Provides sensible defaults and throws clear errors for missing required variables.src/routes/index.js: Route aggregator that imports all route modules and mounts them under their respective prefixes. This keeps the route mounting logic organized and allows routes to be enabled/disabled via configuration.src/services/index.js: Service initializer that creates and connects all services (cache, queue, event listener, WebSocket) in the correct order. Handles graceful shutdown of each service.This modular structure follows the industry-standard separation of concerns used in production Express applications and makes each component independently testable.
Technical Context & Impact
app.js,server.js,config/index.js,routes/index.js, andservices/index.js. The oldindex.jsis removed. Test files that import the app need to update their import paths.const app = require('./app')in tests without starting the server is a significant improvement for test speed and simplicity. This refactoring also makes the application structure clearer for new team members.Step-by-Step Implementation Guide
src/config/index.js. Export a frozen object with all configuration values from environment variables. Useprocess.envwith destructuring and default values. Validate required variables (JWT_SECRET,SOROBAN_RPC_URL,REDIS_URLin production) and throw on startup if missing.src/app.js. Import Express, apply middleware in order (helmet, cors, json body parser with size limit, logger, rate limiter, routes fromroutes/index.js, error handler). Export the configuredappinstance. Do not start listening.src/routes/index.js. Import all route modules (auth,admin,analytics,exports, etc.) and mount them under/api/auth,/api/admin, etc. Export a router or a function that mounts routes on the main app.src/services/index.js. ExportinitServices(app)that initializes cache, queue, event listener, WebSocket in dependency order. ExportshutdownServices()that gracefully stops all services. Services should be stored in a module-level registry.src/server.js. Importappandservices/index.js. Create HTTP server fromapp. Initialize services. Start listening on port from config. Registerprocess.on('SIGTERM'/'SIGINT')handlers that shutdown services and close the server gracefully.src/index.jswith a single line:require('./server.js')or keep as the main entry point that requires server.js.main(orstartscript) points tosrc/server.jsorsrc/index.js(which requires server.js). Update test scripts to import fromsrc/app.jsinstead ofsrc/index.js.src/index.jsto now requiresrc/app.jsdirectly. Create a test helpertests/helpers/app.jsthat provides a configured app instance for supertest.tests/unit/app.test.jsthat verifies the app is created without errors, middleware is applied in correct order, and error handler is present. Createtests/integration/server.test.jstesting server startup and graceful shutdown.Verification & Testing Steps
node src/server.js— verify the server starts without errors and logs the expected startup message with port and environment.npm test— verify all existing tests pass. Tests should be faster since they importapp.jsdirectly without server startup.SIGTERMsignal to the running server — verify graceful shutdown: server stops accepting new connections, ongoing requests complete, services shut down, and the process exits cleanly.JWT_SECRET) and start the server — verify a clear error message is thrown at startup indicating which variable is missing.app.jsin a Node REPL or test file — verify it returns an Express application instance without starting the HTTP server.index.js— verify it's minimal (just requires server.js) and all logic has been migrated to the new modules.