Modularizing a Node.js application that uses ExpressJS is essential for keeping the code clean, scalable, and maintainable. In this article, we’ll see how to organize an Express application in a modular way, separating responsibilities into different files and folders.
1. Folder structure
A recommended modular structure could be as follows:
my-app/
├── app.js
├── routes/
│ ├── index.js
│ └── users.js
├── controllers/
│ ├── userController.js
├── models/
│ └── user.js
├── middlewares/
│ └── auth.js
└── config/
└── db.js
2. Entry point: app.js
The app.js
file is the starting point of the application. Here we configure Express and mount the routes.
const express = require('express');
const app = express();
const indexRoutes = require('./routes/index');
const userRoutes = require('./routes/users');
app.use(express.json());
app.use('/', indexRoutes);
app.use('/users', userRoutes);
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
3. Routing
Each file inside the routes/
folder handles a specific group of endpoints.
routes/index.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('Welcome to the API');
});
module.exports = router;
routes/users.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.post('/', userController.createUser);
module.exports = router;
4. Controller
Controllers contain the business logic for each route.
controllers/userController.js
const users = [];
exports.getAllUsers = (req, res) => {
res.json(users);
};
exports.createUser = (req, res) => {
const user = req.body;
users.push(user);
res.status(201).json(user);
};
5. Middleware
Middleware can be defined separately for reusable functions, such as authentication.
middlewares/auth.js
module.exports = (req, res, next) => {
const token = req.headers['authorization'];
if (token === 'secrettoken') {
next();
} else {
res.status(401).json({ message: 'Unauthorized' });
}
};
6. Configuration and models
All configurations (like database connection) should live in config/
, while data models go in models/
.
config/db.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mydb', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
models/user.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: String,
email: String,
});
module.exports = mongoose.model('User', userSchema);
Conclusion
Structuring an ExpressJS app in a modular way results in clearer, more testable, and easier-to-extend code. Separating routing, controllers, middleware, and configuration is a best practice that improves software quality, especially in medium-to-large projects.