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

Vishal Yadav - Oct 18 - - Dev Community

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.

. . . . . .