DEV Community

Sospeter Mong'are
Sospeter Mong'are

Posted on

Express + MySQL Boilerplate Project Structure (Real App Ready)

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
  }
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

index.js

const router = require("express").Router();

router.use("/users", require("./user.routes"));
router.use("/auth", require("./auth.routes"));

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

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 };
};
Enter fullscreen mode Exit fullscreen mode

5. models/

Models represent database access logic.

User.js

const db = require("../config/db");

exports.findAll = () => {
  return db.execute("SELECT * FROM users");
};
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

errorHandler.js

module.exports = (err, req, res, next) => {
  res.status(500).json({
    success: false,
    message: err.message || "Internal Server Error"
  });
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

✅ Bonus: .env Example

PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASS=secret123
DB_NAME=express_app
Enter fullscreen mode Exit fullscreen mode

✅ 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)