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