Directory Structure
A typical Express project directory structure is as follows:
project-name/
|-- node_modules/
|-- public/
| |-- css/
| |-- js/
| |-- images/
|-- views/
|-- routes/
|-- controllers/
|-- models/
|-- middleware/
|-- config/
|-- tests/
|-- .gitignore
|-- package.json
|-- package-lock.json
|-- README.md
|-- app.js
|-- server.jspublic/: Stores static assets such as CSS, JavaScript, and images.views/: Stores view template files.routes/: Stores route modules.controllers/: Stores controller logic for handling business logic and data operations.models/: Stores data models, such as model definitions for ORMs like Sequelize or Mongoose.middleware/: Stores middleware modules.config/: Stores configuration files, such as database connections and environment variables.tests/: Stores test files..gitignore: Lists files and directories to be ignored by Git.package.json: Specifies project dependencies and scripts.app.js: The entry file for the Express application.server.js: The main file for starting the server, loading configurations, and middleware.README.md: Project documentation.
Module Division
Modularity is a core principle of modern JavaScript development. Dividing code into small, reusable modules enhances readability and maintainability.
- Route Modules: Each route module handles a set of related APIs or pages.
- Controller Modules: Each controller module manages a set of related business logic.
- Model Modules: Each model module represents a table or collection in the database.
Dependency Management
Use npm or yarn to manage project dependencies. List all dependencies and their versions explicitly in the package.json file.
{
"dependencies": {
"express": "^4.17.1",
"mongoose": "^6.0.14",
"body-parser": "^1.19.0"
},
"devDependencies": {
"nodemon": "^2.0.13",
"mocha": "^9.1.3",
"chai": "^4.3.4"
}
}Startup Scripts
Define startup and test scripts in package.json to streamline the development process.
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "mocha --recursive tests/"
}Configuration Files
Store environment variables and sensitive information in a .env file, and use the dotenv package to load these variables.
// config/env.js
require('dotenv').config();
module.exports = {
port: process.env.PORT || 3000,
db: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
}
};Routes and Controllers
// routes/users.js
const express = require('express');
const router = express.Router();
const UserController = require('../controllers/UserController');
router.get('/', UserController.index);
router.post('/', UserController.create);
module.exports = router;// controllers/UserController.js
const UserService = require('../services/UserService');
exports.index = (req, res) => {
UserService.getAllUsers()
.then(users => res.json(users))
.catch(err => res.status(500).json({ error: err.message }));
};
exports.create = (req, res) => {
const newUser = req.body;
UserService.createUser(newUser)
.then(user => res.status(201).json(user))
.catch(err => res.status(400).json({ error: err.message }));
};Data Models
If using MongoDB and Mongoose, a model definition might look like this:
// models/User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
}, { timestamps: true });
module.exports = mongoose.model('User', UserSchema);Middleware
Middleware handles tasks shared across multiple routes, such as logging, error handling, and authentication.
// middleware/logger.js
const morgan = require('morgan');
module.exports = morgan('combined');Testing
Write unit and integration tests to ensure code quality and stability.
// tests/controllers/UserController.test.js
const chai = require('chai');
const sinon = require('sinon');
const sinonChai = require('sinon-chai');
const UserService = require('../../services/UserService');
const UserController = require('../../controllers/UserController');
chai.use(sinonChai);
const expect = chai.expect;
describe('UserController', () => {
it('should return all users', async () => {
const req = {};
const res = {};
sinon.stub(UserService, 'getAllUsers').resolves([{ id: 1 }, { id: 2 }]);
res.json = sinon.spy();
await UserController.index(req, res);
expect(res.json).to.have.been.calledWith([{ id: 1 }, { id: 2 }]);
UserService.getAllUsers.restore();
});
});



