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.
Top comments (71)
While the micro scheme is fine, it lacks a proper repository structuring.
As an example, not aggregating the source code in a
src
directory is a huge misstep. You might want to use TypeScript or some other tooling (e.g., a bundler) to process your code for performance benefits. As such putting everything into the root / mixing with your tooling and tests is something you want to avoid strongly.(You should not use CommonJS - use ESModules; especially if you really care about "maintainability, scalability, and collaboration".)
if i want to convert the javascript application in ES module to a desktop application using electronjs, how do i do it?
Electron.js understands ESM (electronjs.org/docs/latest/tutoria...) - but if you want to only use ESM in development and another format (e.g., CommonJS) at runtime I recommend using a tool such as esbuild (which can bundle and transform).
Thanks for the new insight.
using tools like Electron Packager or Electron Builder.
you are wrong since the moment you want to put types to a non typed language, if want types go and do you stuff in c# or java
My comment was independent if you like or want to use TypeScript. That was one example. Having a
src
directory is pretty much always helpful (as you might want to bundle for performance reasons).Maybe instead of making useless comments it would be great to improve your knowledge about JS and its ecosystem from time to time. Might help.
If you had a good understanding of js you wouldn't have to use typescript in the first place, and secondly you've obviously never worked on any big enough project where the ShitTypescript spends 2 hours making bundles for perforance reasons at every line change
Working on a big project without needing Typescript? You are the one who sounds like you have never worked on any big one. That is Typescript's purpose: to add typing syntax for js, to let the code document itself. So when the project is scaled up, people can join and easily follow up the codebase.
Yeah , you are right , I'm solo freelancer! but it will help who is new in this field.
nice work, thank you for this, for a react frontend that has typescript in it, how should the folder structure be arranged?
Just put everything in a source and write the code as-is. (e.g., then src/index.tsx instead of a index.js or src/index.js). Use a tool such as esbuild to consume the source files and transform (+ bundle) them into a dist folder (src/index.tsx and related files -> dist/app.js).
It's a standard practice that pretty much all applications today follow.
I will share soon!
Thatβs well said but if we are really aiming that big and complex applications, then we should use another frameworks like nestjs which provide all these things out of the box.
I agree, this is fine for JS, but any serious project should really be using Typescript as you said, type-safe SDKs are super powerful, see Tanstack Router or others that are built with this in mind.
Very good article π―. Structuring projects is a more difficult task than it seems because we have to think about possible developments and how they can be accommodated in the least painful way possible.
If you allow me, I would just like to make a suggestion for improvement.
The regex used to validate email is vulnerable to ReDos (Regex Denial Of Service). It would be interesting to perhaps try to replace it with another one in the article so that other people who come to read the article don't end up using this regex to validate data in real systems.
Here's a tip for readers, always validate your regex before using.
I generally use this site to check if a regex is vulnerable: devina.io/redos-checker
Once again congratulations on the article π
Thanks!
I have always had a bit of a problem with this type of folder structure. I don't understand how you can modularize your code with it. You have, at the root of source directory, the typical controllers, services, repositories (application, domain and data layers) folders and inside those you have similarly named files and classes, like "customer" or "product". While the controller, the service and the repository for "customer" belong together, they aren't close to each other, so it's harder to make that connection.
I always considered it better to have a
modules/
folder inside which I have thecustomer
andproduct
and inside those folders have layers likecontrollers/
,services/
,views/
andpersistence/
orrepositories/
etc. This way I can easily focus on working inside one module, and if I need to, I can easily separate it to it's own service.A tip there is to use DDD, so that you create modules based on the bounded contexts and you can easily create microservices based on those bounded contexts. Yeah, you will have more nested folders, but you still have a more clear grouping and separating them later (if needed) is much less painful.
horrible i hate it its dumb
The code looks well organized and readable
Thanks!
Thanks for this amazing writeup, Vishal. β€οΈed it!
It'll be great if you can share a sample GitHub repo for referencing a similar project structure. Would be a great addition as a reference material.
That said, I recently came up with a backend microservice template using JavaScript, Node.js, and Express.js. Although incomplete, it's quite practical and can be used to build reliable backend services. In my template though I've taken inspiration from NestJS for their modular approach to the directory structure.
Your blog though is a great reference for me to build another template, one that corresponds to the more common way of organizing the project, the way you've portrayed here.
So again, thanks! π―
Thanks, bro means lot
Not going to lie this directory structure is archaic and for beginners who are new to expressjs. There is are far more approachable and preferable ways to structure an expressjs app, most especially when working with typescript at the backend.
Well, i came across this article through daily.dev and gosh it helps me a lot, because i'm in the middle of structuring my backend in an important project. I tried structuring based on this -> github.com/goldbergyoni/nodebestpr...
Now i realize, @vyan article is what i need to go on.
Thanks for sharing.
Good job this is well done. I think for me as a beginner it's the principle. whichever approach one choses it must satisfy the principle of separation of concerns. For those criticizing I think it would be best to support your claim with a well organized and concise alternative as this presented herein rather than hate and throw things here and there. Once again good job @vyan π°πͺ
It might have been better to dive into one of the popular start kits and explore why they are structured that way.
But this is a good one nonetheless!
Thanks!
Can you share the links for those popular start kits?
Yeah
Nice insights!! @vyan
Thanks!
Some comments may only be visible to logged-in visitors. Sign in to view all comments.