DEV Community

Cover image for How to Structure Your Backend Code in Node.js (Express.js)
Vishal Yadav
Vishal Yadav

Posted on

How to Structure Your Backend Code in Node.js (Express.js)

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

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

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

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

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

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

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

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

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

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

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

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

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)

Collapse
 
florianrappl profile image
Florian Rappl

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".)

Collapse
 
adesoji1 profile image
Adesoji1

if i want to convert the javascript application in ES module to a desktop application using electronjs, how do i do it?

Collapse
 
florianrappl profile image
Florian Rappl

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

Thread Thread
 
dev_dive profile image
Chibueze Onyekpere

Thanks for the new insight.

Collapse
 
vyan profile image
Vishal Yadav

using tools like Electron Packager or Electron Builder.

Collapse
 
jjco790923 profile image
Juan JosΓ© Cruz Ortiz

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

Collapse
 
florianrappl profile image
Florian Rappl

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.

Thread Thread
 
jjco790923 profile image
Juan JosΓ© Cruz Ortiz

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

Thread Thread
 
nguyendat251 profile image
NguyenDat251

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.

Thread Thread
 
vyan profile image
Vishal Yadav

Yeah , you are right , I'm solo freelancer! but it will help who is new in this field.

Collapse
 
adesoji1 profile image
Adesoji1

nice work, thank you for this, for a react frontend that has typescript in it, how should the folder structure be arranged?

Collapse
 
florianrappl profile image
Florian Rappl

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.

Collapse
 
vyan profile image
Vishal Yadav

I will share soon!

Collapse
 
asisshukla profile image
Ashish Shukla

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.

Collapse
 
clarenceliu86 profile image
Clarence Liu

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.

Collapse
 
r9n profile image
Ronaldo Modesto

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 πŸ˜ƒ

Collapse
 
vyan profile image
Vishal Yadav

Thanks!

Collapse
 
rcls profile image
OssiDev • Edited

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 the customer and product and inside those folders have layers like controllers/, services/, views/ and persistence/ or repositories/ 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.

Collapse
 
jjco790923 profile image
Juan JosΓ© Cruz Ortiz

horrible i hate it its dumb

Collapse
 
omara_patrick_809b3562f29 profile image
Omara Patrick

The code looks well organized and readable

Collapse
 
vyan profile image
Vishal Yadav

Thanks!

Collapse
 
yourakshaw profile image
Ayush Kumar Shaw

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! πŸ’―

Collapse
 
vyan profile image
Vishal Yadav

Thanks, bro means lot

Collapse
 
beauspot profile image
Iyere Handsome(Beau) Omogbeme

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.

Collapse
 
pl_andrsciocanandris profile image
PΓ‘l AndrΓ‘s Ciocan (Andris)

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.

Collapse
 
jokello profile image
James Oduor • Edited

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 πŸ‡°πŸ‡ͺ

Collapse
 
array_dot_reduce profile image
Bhaskar Ghale

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!

Collapse
 
vyan profile image
Vishal Yadav

Thanks!

Collapse
 
juka0920 profile image
Joana Pereira

Can you share the links for those popular start kits?

Collapse
 
vyan profile image
Vishal Yadav

Yeah

Collapse
 
srishtikprasad profile image
Srishti Prasad

Nice insights!! @vyan

Collapse
 
vyan profile image
Vishal Yadav

Thanks!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.