Skip to content

[Refactor] Split Monolithic index.js into Modular Express Application Structure #28

Description

@KarenZita01

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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

  1. Run node src/server.js — verify the server starts without errors and logs the expected startup message with port and environment.
  2. Run npm test — verify all existing tests pass. Tests should be faster since they import app.js directly without server startup.
  3. 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.
  4. 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.
  5. Import app.js in a Node REPL or test file — verify it returns an Express application instance without starting the HTTP server.
  6. Check the final index.js — verify it's minimal (just requires server.js) and all logic has been migrated to the new modules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions