project-root/
│
├── src/
│ ├── config/
│ │ ├── db.js
│ │ └── index.js
│ │
│ ├── controllers/
│ │ ├── AuthController.js
│ │ ├── UserController.js
│ │ └── ExampleController.js
│ │
│ ├── routes/
│ │ ├── auth.routes.js
│ │ ├── user.routes.js
│ │ └── index.js
│ │
│ ├── services/
│ │ ├── AuthService.js
│ │ ├── UserService.js
│ │ └── ExampleService.js
│ │
│ ├── models/
│ │ ├── User.js
│ │ ├── index.js
│ │ └── migrations/
│ │ └── 001_create_users_table.sql
│ │
│ ├── middleware/
│ │ ├── authMiddleware.js
│ │ ├── errorHandler.js
│ │ └── validateRequest.js
│ │
│ ├── utils/
│ │ ├── logger.js
│ │ └── helpers.js
│ │
│ ├── app.js
│ └── server.js
│
├── .env
├── .gitignore
├── package.json
└── README.md
✅ Folder Breakdown: What Each Part Does
1. config/
This is the control tower.
Houses database and environment configurations.
db.js
const mysql = require("mysql2/promise");
require("dotenv").config();
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10
});
module.exports = pool;
2. controllers/
Controllers are the “traffic cops.”
They receive requests, call the service layer, and return responses.
UserController.js
const UserService = require("../services/UserService");
exports.createUser = async (req, res, next) => {
try {
const data = await UserService.createUser(req.body);
res.status(201).json({ success: true, data });
} catch (err) {
next(err);
}
};
3. routes/
Routes connect URLs to controllers.
Keeps app.js clean and modular.
user.routes.js
const express = require("express");
const router = express.Router();
const UserController = require("../controllers/UserController");
router.post("/", UserController.createUser);
router.get("/", UserController.getAllUsers);
module.exports = router;
index.js
const router = require("express").Router();
router.use("/users", require("./user.routes"));
router.use("/auth", require("./auth.routes"));
module.exports = router;
4. services/
Business logic lives here.
Services talk to models and keep controllers lightweight.
UserService.js
const db = require("../config/db");
exports.createUser = async (data) => {
const [result] = await db.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
[data.name, data.email]
);
return { id: result.insertId, ...data };
};
5. models/
Models represent database access logic.
User.js
const db = require("../config/db");
exports.findAll = () => {
return db.execute("SELECT * FROM users");
};
6. middleware/
Reusable logic that sits between request and controller.
Examples:
- Authentication
- Request validation
- Error handling
authMiddleware.js
module.exports = (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ message: "Unauthorized" });
}
next();
};
errorHandler.js
module.exports = (err, req, res, next) => {
res.status(500).json({
success: false,
message: err.message || "Internal Server Error"
});
};
7. utils/
Helper functions, loggers, etc.
8. app.js & server.js
app.js
Assembles routes and middleware.
const express = require("express");
const app = express();
const routes = require("./routes");
const errorHandler = require("./middleware/errorHandler");
app.use(express.json());
app.use("/api", routes);
app.use(errorHandler);
module.exports = app;
server.js
Runs the app.
require("dotenv").config();
const app = require("./app");
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
✅ Bonus: .env Example
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASS=secret123
DB_NAME=express_app
✅ Why This Structure Works in Real Apps
✅ Separates logic by responsibility
✅ Helps you scale: add more features without chaos
✅ Easier debugging
✅ Clean, readable, team-friendly
✅ Mirrors modern frameworks (Laravel, Django style)
✅ Production-grade layout
Top comments (0)