Logo

dev-resources.site

for different kinds of informations.

Mastering Backend Node.js Folder Structure, A Beginner’s Guide

Published at
1/12/2025
Categories
node
javascript
backend
architecture
Author
jovickcoder
Author
11 person written this
jovickcoder
open
Mastering Backend Node.js Folder Structure, A Beginner’s Guide

Table of Contents

Introduction

Organizing your codebase is one of the most important yet overlooked aspects of building a backend. Without a proper folder structure, even small projects can quickly become messy, making debugging and scaling a headache. If you’ve ever struggled to figure out where your code should go, you’re not alone—it’s a common challenge for beginners.

In this guide, we’ll simplify the process of structuring your Node.js backend. Starting with a beginner-friendly setup, we’ll explore the purpose of each folder, provide practical examples, and share best practices. By the end, you’ll have a clear roadmap to keep your projects clean, maintainable, and ready to grow.

Why a Good Folder Structure Matters

A good folder structure is like a well-organized toolbox. It makes your life easier and ensures your project can:

  • Scale: Grow without turning into a tangled mess.
  • Collaborate: Make sense to other developers on your team.
  • Debug: Quickly identify and fix issues. Even for small projects, starting with a clean structure saves time in the long run.

The Basic Folder Structure for Beginners

Overview of the Structure

project/
├── controllers/
├── models/
├── routes/
├── middlewares/
├── utils/
├── config/
├── server.js
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Setup:

Initialize Your Project:

Run the following commands in your terminal:

mkdir my-project
cd my-project
npm init -y
npm install express

Enter fullscreen mode Exit fullscreen mode

Create Your Folders:

Use these commands to set up the structure:

mkdir controllers models routes middlewares utils config
touch server.js
Enter fullscreen mode Exit fullscreen mode

Breaking Down the Folders:

controllers/

What it is:

The "brain" of your application. Controllers contain the logic that handles incoming requests and decides what data to send back to the client.

When to use it:
Every time a user sends a request (e.g., via an API call), the controller processes the request, interacts with the database if needed, and sends back the appropriate response.

Example:

Let’s say you have a user who wants to fetch a list of all registered users. The controller will:

  1. Receive the request.
  2. Fetch the user data from the database using the model.
  3. Send the data back to the client.

Code Example:

// controllers/userController.js
const User = require('../models/User');

// Fetch all users
exports.getUsers = async (req, res) => {
    try {
        const users = await User.find(); // Query the database for users
        res.status(200).json(users);    // Send data to the client
    } catch (error) {
        res.status(500).json({ error: 'Something went wrong' });
    }
};

// Add a new user
exports.createUser = async (req, res) => {
    try {
        const newUser = new User(req.body); // Create a new user instance
        await newUser.save();              // Save it to the database
        res.status(201).json(newUser);     // Respond with the created user
    } catch (error) {
        res.status(400).json({ error: 'Invalid input' });
    }
};
Enter fullscreen mode Exit fullscreen mode

models/

What it is:

The "blueprint" of your data. Models define the structure of your data and how it interacts with the database.

When to use it:

Whenever you need to save, retrieve, or manipulate data in your database, you use models. They enforce consistency in the data format (e.g., all users must have a name and email).

Example:

If you’re working with a MongoDB database, you might use Mongoose to define a model for users. This ensures that every user document has a consistent structure.

Code Example:

// models/User.js
const mongoose = require('mongoose');

// Define the schema for a user
const userSchema = new mongoose.Schema({
    name: { type: String, required: true },       // Name is required
    email: { type: String, required: true },     // Email is required and unique
    createdAt: { type: Date, default: Date.now } // Auto-generate timestamps
});

// Export the model for use in controllers
module.exports = mongoose.model('User', userSchema);

Enter fullscreen mode Exit fullscreen mode

routes/

What it is:

The "map" of your application. Routes connect specific URLs (or endpoints) to the appropriate controller functions.

When to use it:

Every time you want to create a new endpoint (e.g., /users or /products), you define it in a route file. Routes ensure that incoming requests are directed to the correct controller.

Example:

For a /users endpoint, the route file will link the URL to the controller functions like getUsers or createUser.

Code Example:

// routes/userRoutes.js
const express = require('express');
const { getUsers, createUser } = require('../controllers/userController');
const router = express.Router();

// Map URLs to controller functions
router.get('/', getUsers);     // GET /users → fetch all users
router.post('/', createUser);  // POST /users → create a new user

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

middlewares/

What it is:

The "gatekeeper" of your application. Middleware functions run before your controllers, allowing you to handle tasks like authentication, logging, or input validation.

When to use it:

Whenever you need to process a request before it reaches the controller. For example, you can check if a user is authenticated or log details about incoming requests.

Example:

Let’s say you want to log every request your server receives. A middleware can handle this.

Code Example:

// middlewares/logger.js
module.exports = (req, res, next) => {
    console.log(`${req.method} request to ${req.url}`);
    next(); // Pass control to the next middleware or controller
};
Enter fullscreen mode Exit fullscreen mode

utils/

What it is:

The "toolbox" of your application. Utility files store helper functions or modules that can be reused across your project.

When to use it:

Any time you have code that doesn’t belong in a controller, model, or middleware but needs to be used in multiple places. Examples include formatting dates, hashing passwords, or sending emails.

Example:
Imagine you need a reusable function to format timestamps.

Code Example:

// utils/dateFormatter.js
exports.formatDate = (date) => {
    return new Date(date).toLocaleDateString('en-US');
};
Enter fullscreen mode Exit fullscreen mode

config/

What it is:

The "control center" of your application. This folder stores configuration files, such as database connection settings, API keys, or environment variables.

When to use it:

Any time you need to configure your app for different environments (e.g., development, testing, production).

Example:

Here’s a file to handle your MongoDB connection:

Code Example:

// config/db.js
const mongoose = require('mongoose');

const connectDB = async () => {
    try {
        await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true });
        console.log('Database connected successfully');
    } catch (error) {
        console.error('Database connection failed:', error.message);
        process.exit(1); // Exit the process with failure
    }
};

module.exports = connectDB;
Enter fullscreen mode Exit fullscreen mode

server.js

What it is:

The "heart" of your application. This file initializes the Express server, connects to the database, and sets up the middleware and routes.

When to use it:

Every project needs an entry point, and this is it.

Example:
Here’s a simple example of a server.js file:

Code Example:

// server.js
require('dotenv').config(); // Load environment variables
const express = require('express');
const connectDB = require('./config/db');
const userRoutes = require('./routes/userRoutes');

const app = express();

// Middleware
app.use(express.json()); // Parse JSON
app.use(require('./middlewares/logger')); // Log incoming requests

// Connect to the database
connectDB();

// Routes
app.use('/users', userRoutes);

// Start the server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

Advanced Folder Structure for Scalability

As your application grows in complexity, a more organized and scalable folder structure becomes necessary. Here's a detailed breakdown of the advanced folder structure:

project/
├── src/
│   ├── controllers/
│   ├── models/
│   ├── routes/
│   ├── middlewares/
│   ├── utils/
│   ├── config/
│   ├── services/
│   ├── tests/
│   ├── app.js
├── public/
├── package.json
├── README.md

Enter fullscreen mode Exit fullscreen mode

Breakdown of Additional Folders

services/

Business Logic Layer

The services folder contains reusable business logic that doesn't belong in the controller. This helps keep your controllers clean and focused solely on handling HTTP requests and responses.

Example Use Case:

You need to calculate discounts for an e-commerce application. Instead of writing this logic in the controller, you create a discountService.js file.

Code Example:

// services/discountService.js
exports.calculateDiscount = (price, discountRate) => {
    return price - (price * discountRate);
};

Enter fullscreen mode Exit fullscreen mode

Then, your controller can simply call this function:

// controllers/productController.js
const { calculateDiscount } = require('../services/discountService');

exports.getProduct = async (req, res) => {
    const product = await Product.findById(req.params.id);
    const discountedPrice = calculateDiscount(product.price, 0.1);
    res.json({ product, discountedPrice });
};
Enter fullscreen mode Exit fullscreen mode

tests/

Testing Code
The tests folder is essential for maintaining code quality as your app grows. It contains unit tests, integration tests, and sometimes end-to-end (E2E) tests.

Best Practices:

  • Use a testing library like Jest, Mocha, or Supertest.
  • Organize tests to mirror your src folder structure. For example, a test for controllers/userController.js might be in tests/controllers/userController.test.js.

Code Example:

// tests/controllers/userController.test.js
const request = require('supertest');
const app = require('../../src/app');

describe('GET /users', () => {
    it('should return a list of users', async () => {
        const response = await request(app).get('/users');
        expect(response.status).toBe(200);
        expect(response.body).toBeInstanceOf(Array);
    });
});
Enter fullscreen mode Exit fullscreen mode

public/

Static Files

The public folder is where you store static assets like images, CSS, JavaScript, or fonts. These files are served directly to the client without being processed by your backend.

Use Case:

Serving a logo image for your application.
Hosting frontend files if you’re building a full-stack app with client-side rendering.

Example:

An image stored in public/images/logo.png can be accessed via http://yourdomain.com/images/logo.png.

Code Configuration:

To serve static files, configure Express like this:

const express = require('express');
const app = express();

app.use(express.static('public')); // Serves files in the 'public' directory
Enter fullscreen mode Exit fullscreen mode

app.js

Application Setup

The app.js file initializes the core of your application. It’s typically used to:

  • Set up middleware.
  • Register routes.
  • Export the Express app for server configuration or testing.

Code Example:

// src/app.js
const express = require('express');
const userRoutes = require('./routes/userRoutes');
const loggerMiddleware = require('./middlewares/logger');

const app = express();
app.use(express.json());
app.use(loggerMiddleware);
app.use('/users', userRoutes);

module.exports = app; // Export for testing or server.js
Enter fullscreen mode Exit fullscreen mode

Why This Structure Works for Scalability

  1. Separation of Concerns:
    Each folder has a specific responsibility, making the codebase easier to understand and maintain.

  2. Reusability:
    The services folder keeps your business logic reusable and avoids duplication.

  3. Testability:
    A clear structure ensures every part of your app is easy to test, leading to fewer bugs.

  4. Ease of Collaboration: Teams can work on different parts of the app without stepping on each other’s toes.

Best Practices for Organizing Your Code

  1. Start Small, Scale Later: Begin with a simple structure and refactor as your app grows.
  2. Stick to Conventions: Use clear and consistent naming.
  3. Stay DRY (Don’t Repeat Yourself): Extract repetitive logic into reusable utilities.
  4. Use Environment Variables: Keep sensitive data like API keys out of your codebase.
  5. Document Everything: Include a README with explanations for your setup.
  6. Test Regularly: Add tests to catch bugs early.

Conclusion

Organizing your Node.js backend may seem daunting at first, but a good folder structure will save you time, reduce bugs, and make your code easy to maintain. Whether you’re starting small or building a large-scale application, following these guidelines will set you on the path to success.

Ready to dive deeper? Stay tuned for the REST API-focused structure coming next!

Wrap-Up and Feedback 💬

Thank you for taking the time to read this article! I hope it helped simplify the topic for you and provided valuable insights. If you found it helpful, consider following me for more easy-to-digest content on web development and other tech topics.

Your feedback matters! Share your thoughts in the comments section—whether it's suggestions, questions, or areas you'd like me to improve. Feel free to use the reaction emojis to let me know how this article made you feel. 😊


Stay Connected 🌐

I’ve been sharing my journey and insights for a while, and I’d love to connect with you! Let’s continue exchanging ideas, learning from each other, and growing together.

Follow me on my socials and let’s stay in touch:

Looking forward to hearing from you and growing this community of curious minds! 🚀

architecture Article's
30 articles in total
Favicon
Mastering Essential Software Architecture Patterns: A Comprehensive Guide🛠️, Part 6
Favicon
MVVM directory structure for larger project
Favicon
Solving Circular Dependencies: A Journey to Better Architecture
Favicon
微前端
Favicon
Como redes peer-to-peer funcionam?
Favicon
Things About Contexts in Front-end Projects
Favicon
The Myth of the 10x Software Developer
Favicon
[Boost]
Favicon
How to Design a Secure and Scalable Multi-Region Architecture on AWS
Favicon
Token Bucket Rate Limiter (Redis & Java)
Favicon
Streamlining Data Flow in Angular: The Power of the Adapter Pattern 🔄
Favicon
Cqrs
Favicon
Why Schema Compatibility Matters
Favicon
Абстракции vs. привязка к технологии
Favicon
Understanding the Essential Elements of a Well-Designed CISC Architecture for Modern Computing
Favicon
Things About Modules in Front-end Projects
Favicon
The first part of this MASSIVE series about software architecture patterns is OUT!! please check it out!!
Favicon
Designing Context for New Modules in HyperGraph
Favicon
Patterns of Directory Structure in Front-end Projects
Favicon
Mastering Backend Node.js Folder Structure, A Beginner’s Guide
Favicon
What Makes a Good Cloud Architect?
Favicon
Optimizing Module Development in HyperGraph: A Minimalist Approach
Favicon
The Future of Architecture: Where Innovation Meets Sustainability
Favicon
Top AI Tools for Architects and Interior Designers
Favicon
Singleton ou Observable? A Escolha Errada Pode Custar Sua Promoção!
Favicon
Do Local ao Global: A Migração para Azure que Aumentou Nossa Eficiência e Segurança
Favicon
Your API Doesn’t Always Need to Be a Product
Favicon
The Future of Architecture is Being Built by Robots
Favicon
The Role of Serverless Architecture in Modern Website Development: Benefits and Impact
Favicon
Understanding Microservices Architecture in Full-Stack Applications

Featured ones: