A single server.js file works fine for a demo, but as an Express app grows, splitting
routes, controllers, models, and configuration into folders keeps things sane.
Why structure matters
- Organization: related files live together.
- Maintainability: easier to find and change code.
- Team-friendly: multiple developers can work on different folders without stepping on each other.
- Scalability: new features slot in without cluttering existing files.
A workable folder layout
bookstore-api/
├── config/
├── controllers/
├── database/
├── middleware/
├── models/
├── public/
├── routes/
├── services/
├── tests/
├── utils/
├── views/
├── .env
├── app.js
├── server.js
├── package.json
└── README.md
config/ — environment and connection settings.
module.exports = {
dbUrl: process.env.DB_URL || "mongodb://localhost:27017/bookstore",
};
controllers/ — request handling logic, kept out of route files.
exports.listBooks = (req, res) => {
res.status(200).json([
{ id: 1, title: "Atomic Habits" },
{ id: 2, title: "Deep Work" },
]);
};
database/ — connection setup.
const { Pool } = require("pg");
const pool = new Pool({
host: "localhost",
user: "admin",
password: "",
database: "bookstore",
});
module.exports = pool;
.env — secrets and environment-specific values, never committed to source control.
DB_URL=mongodb://localhost:27017/bookstore
JWT_SECRET=super-secret-key
middleware/ — reusable request/response logic such as auth checks.
module.exports = (req, res, next) => {
const token = req.headers["authorization"];
if (!token) return res.status(403).send("No token provided");
next();
};
models/ — shape of your data.
const bookSchema = {
id: 1,
title: "Atomic Habits",
author: "James Clear",
};
module.exports = bookSchema;
public/ — static assets served directly to the client.
routes/ — map URLs to controller functions.
const express = require("express");
const router = express.Router();
const bookController = require("../controllers/bookController");
router.get("/books", bookController.listBooks);
module.exports = router;
services/ — business logic kept separate from controllers.
exports.findBookById = (id) => {
return { id, title: "Atomic Habits" };
};
tests/ — unit and integration tests.
utils/ — small reusable helpers.
module.exports = (message) => {
console.log(`[LOG]: ${message}`);
};
views/ — templates for a server-rendered app (EJS, Pug, etc.).
server.js — the entry point that starts listening.
const app = require("./app");
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
app.js — assembles middleware and routes, then exports the app.
const express = require("express");
const app = express();
app.use(express.json());
app.use("/", require("./routes/bookRoutes"));
module.exports = app;
package.json — dependencies, scripts, metadata.
README.md — setup and usage instructions for anyone new to the project.