When developing a Node.js application using Express.js, structuring your codebase effectively is crucial for maintainability, scalability, and ease of collaboration. A well-organized project structure allows you to manage complexity, making it easier to navigate and understand the code. In this blog, we’ll explore a typical folder structure for an Express.js application and explain the purpose of each directory and file.
Project Structure Overview
Hereβs a common folder structure for an Express.js application:
π
βββ π app.js
βββ π bin
βββ π config
βββ π controllers
β βββ π customer.js
β βββ π product.js
β βββ ...
βββ π middleware
β βββ π auth.js
β βββ π logger.js
β βββ ...
βββ π models
β βββ π customer.js
β βββ π product.js
β βββ ...
βββ π routes
β βββ π api.js
β βββ π auth.js
β βββ ...
βββ π public
β βββ π css
β βββ π js
β βββ π images
β βββ ...
βββ π views
β βββ π index.ejs
β βββ π product.ejs
β βββ ...
βββ π tests
β βββ π unit
β βββ π integration
β βββ π e2e
β βββ ...
βββ π utils
β βββ π validation.js
β βββ π helpers.js
β βββ ...
βββ π node_modules
Explanation of Each Directory and File
app.js
The app.js
file is the entry point of your application. Itβs where you initialize the Express app, set up middleware, define routes, and start the server. Think of it as the control center of your web application.
const express = require('express');
const app = express();
const config = require('./config');
const routes = require('./routes');
// Middleware setup
app.use(express.json());
// Routes setup
app.use('/api', routes);
// Start server
const PORT = config.port || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
module.exports = app;
bin
The bin
directory typically contains scripts for starting your web server. These scripts can be used to set environment variables or manage different environments (e.g., development, production).
Example: bin/www
#!/usr/bin/env node
const app = require('../app');
const debug = require('debug')('your-app:server');
const http = require('http');
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const server = http.createServer(app);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
function normalizePort(val) {
const port = parseInt(val, 10);
if (isNaN(port)) return val;
if (port >= 0) return port;
return false;
}
function onError(error) {
if (error.syscall !== 'listen') throw error;
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
function onListening() {
const addr = server.address();
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
debug('Listening on ' + bind);
}
config
The config
directory holds configuration files for your application, such as database connections, server settings, and environment variables.
Example: config/index.js
module.exports = {
port: process.env.PORT || 3000,
db: {
host: 'localhost',
port: 27017,
name: 'mydatabase'
}
};
controllers
Controllers contain the logic for handling incoming requests and generating responses. Each file in the controllers
directory typically corresponds to a different part of your application (e.g., customers, products).
Example: controllers/customer.js
const Customer = require('../models/customer');
exports.getAllCustomers = async (req, res) => {
try {
const customers = await Customer.find();
res.json(customers);
} catch (err) {
res.status(500).json({ message: err.message });
}
};
middleware
Middleware functions are used to process requests before they reach the controllers. They can handle tasks like authentication, logging, and request validation.
Example: middleware/auth.js
module.exports = (req, res, next) => {
const token = req.header('Authorization');
if (!token) return res.status(401).json({ message: 'Access Denied' });
try {
const verified = jwt.verify(token, process.env.JWT_SECRET);
req.user = verified;
next();
} catch (err) {
res.status(400).json({ message: 'Invalid Token' });
}
};
models
Models define the structure of your data and handle interactions with the database. Each model file typically corresponds to a different data entity (e.g., Customer, Product).
Example: models/customer.js
const mongoose = require('mongoose');
const customerSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Customer', customerSchema);
routes
Routes define the paths to different parts of your application and map them to the appropriate controllers.
Example: routes/api.js
const express = require('express');
const router = express.Router();
const customerController = require('../controllers/customer');
router.get('/customers', customerController.getAllCustomers);
module.exports = router;
public
The public
directory contains static files like CSS, JavaScript, and images that are served directly to the client.
Example: Directory Structure
public/
βββ css/
βββ js/
βββ images/
views
Views are templates that render the HTML for the client. Using a templating engine like EJS, Pug, or Handlebars, you can generate dynamic HTML.
Example: views/index.ejs
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<h1>Welcome to My App</h1>
<div id="content">
<%- content %>
</div>
</body>
</html>
tests
The tests
directory contains test files to ensure your application works correctly. Tests are often organized into unit tests, integration tests, and end-to-end (e2e) tests.
Example: Directory Structure
tests/
βββ unit/
βββ integration/
βββ e2e/
utils
Utility functions and helper modules are stored in the utils
directory. These functions perform common tasks like validation and formatting that are used throughout the application.
Example: utils/validation.js
exports.isEmailValid = (email) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(String(email).toLowerCase());
};
node_modules
The node_modules
directory contains all the dependencies your project needs. This directory is managed by npm (or yarn) and includes packages installed from the npm registry.
Conclusion
A well-structured Node.js application using Express.js enhances maintainability, scalability, and collaboration. Each directory and file in the structure serves a specific purpose, from handling configuration and defining routes to managing middleware and rendering views. By organizing your codebase effectively, you can build robust and scalable applications with ease.