Modularizing a Node.js application with ExpressJS

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.

Back to top