DEV Community

Kafeel Ahmad (kaf shekh)
Kafeel Ahmad (kaf shekh)

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 (0)