In a typical beginner setup, we often place all logic (server configuration, routes, database connection) inside a single file like index.ts
.
But as your project grows, this becomes:
-
๐งน Messy
-
๐ฐ Hard to manage
-
๐ Against best practices like Single Responsibility Principle
๐งจ Current Problem: Everything in index.ts
// index.ts import * as express from ‘express’; import * as mongoose from ‘mongoose’; import { getEnvironmentVariables } from ‘./environments/env’; const app = express(); app.listen(5000, () => { console.log(‘Server is running’); }); // DB Connection Logic const DB_URI = getEnvironmentVariables().db_url; mongoose.connect(DB_URI, { dbName: ‘test’ }) .then(() => console.log(‘โ MongoDB connected’)) .catch(err => console.error(‘โ Mongo Error:’, err.message)); // Route Handlers app.get(‘/login’, (req, res) => res.send(‘Login logic’)); app.get(‘/test’, (req, res) => res.send(‘Test route’));
This is fine for learning, but not scalable or clean.
โ Goal: A Clean, Modular Project Structure
We’ll split responsibilities into separate files and folders:
-
server.ts
โ App logic & server startup -
routes/
โ Route definitions -
controllers/
โ Handle request logic -
index.ts
โ Entry point
๐ Step-by-Step Refactoring
๐ 1. Create server.ts
Handles:
-
MongoDB connection
-
Route registration
// server.ts import * as express from ‘express’; import * as mongoose from ‘mongoose’; import { getEnvironmentVariables } from ‘./environments/env’; import UserRouter from ‘./routes/UserRouter’; export class Server { public app: express.Application = express(); constructor() { this.setConfigurations(); this.setRoutes(); } setConfigurations() { this.connectMongoDb(); } connectMongoDb() { const DB_URI = getEnvironmentVariables().db_url; mongoose.connect(DB_URI, { dbName: ‘test’ }) .then(() => console.log(‘โ MongoDB connected successfully’)) .catch(err => { console.error(‘โ MongoDB connection error:’, err.message); process.exit(1); }); mongoose.connection.on(‘connected’, () => { console.log(‘Mongoose connected to DB’); }); mongoose.connection.on(‘error’, (err) => { console.log(‘Mongoose connection error:’, err); }); mongoose.connection.on(‘disconnected’, () => { console.log(‘Mongoose disconnected’); }); } setRoutes() { this.app.use(‘/api/user’, UserRouter); // all user-related routes } }
๐ 2. Create routes/UserRouter.ts
Handles:
-
Route paths (
/login
,/signup
) -
Forwards logic to controllers
// routes/UserRouter.ts import { Router } from ‘express’; import { UserController } from ‘../controllers/UserController’; export class UserRouter { public router: Router; constructor() { this.router = Router(); this.getRoutes(); this.postRoutes(); this.patchRoutes(); this.deleteRoutes(); } getRoutes() { this.router.get(‘/login’, UserController.login); } postRoutes() {} patchRoutes() {} deleteRoutes() {} } export default new UserRouter().router;
๐ 3. Create controllers/UserController.ts
Handles:
-
Request โ Response logic (business logic)
// controllers/UserController.ts export class UserController { static login(req, res) { res.send(‘We are here to login’); } }
๐ข 4. Update index.ts
Entry point for your project. Simple and clean.
// index.ts import { Server } from ‘./server’; const server = new Server().app; const port = 5000; server.listen(port, () => { console.log(`๐ Server is running on port ${port}`); });
๐ฆ Final Project Structure
nodejs-project/ โโโ node_modules/ โโโ src/ โ โโโ controllers/ โ โ โโโ UserController.ts โ โโโ environments/ โ โ โโโ dev.env.ts โ โ โโโ env.ts โ โ โโโ prod.env.ts โ โโโ routes/ โ โ โโโ UserRouter.ts โ โโโ server.ts โ โโโ index.ts โโโ package.json โโโ package-lock.json โโโ tsconfig.json
๐ Testing Your Refactored API
Use Postman or browser:
GET Request to:
http://localhost:5000/api/user/login
โ Output:
We are here to login
๐ง Visual Flow Diagram
index.ts โ server.ts โ configures Express + MongoDB โ routes/UserRouter.ts โ defines /login route โ controllers/UserController.ts โ handles actual login logic
โจ Benefits of This Structure
โ
Follows Single Responsibility Principle
โ
Clean separation of concerns
โ
Easy to scale with new features (like auth, admin, product, etc.)
โ
Understandable and maintainable for any developer