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! 🚀

backend Article's
30 articles in total
Favicon
[Boost]
Favicon
Singularity: Streamlining Game Development with a Universal Framework
Favicon
5 Tools Every Developer Should Know in 2025
Favicon
Preventing SQL Injection with Raw SQL and ORM in Golang
Favicon
Como redes peer-to-peer funcionam?
Favicon
🌐 Building Golang RESTful API with Gin, MongoDB 🌱
Favicon
What is Quartz.Net and its simple implementation
Favicon
tnfy.link - What's about ID?
Favicon
Construindo uma API segura e eficiente com @fastify/jwt e @fastify/mongodb
Favicon
Desbravando Go: Capítulo 1 – Primeiros Passos na Linguagem
Favicon
Understanding Spring Security and OAuth 2.0
Favicon
RabbitMQ: conceitos fundamentais
Favicon
Mastering Java: A Beginner's Guide to Building Robust Applications
Favicon
Mastering Backend Node.js Folder Structure, A Beginner’s Guide
Favicon
Setting Up Your Go Environment
Favicon
Introducing Java Library for Backend Microservice Webflux (Reactor-core)
Favicon
SQL Injection - In Just 5 Minutes!
Favicon
10 Backend Terms Every Frontend Developer Should Know
Favicon
How Do You Use Encapsulation with Micronaut Annotations?
Favicon
Building Streak Calendar: My Journey into Open-Source with the Help of AI
Favicon
Authentication System Using NodeJS
Favicon
Digesto: A Lightning-Fast Way to Build Backends with YAML
Favicon
dotnet терминал команды
Favicon
My Study Schedule for 2025
Favicon
Building Microservices with Node.js: An Introduction
Favicon
Great resource for backend developers
Favicon
[Boost]
Favicon
6 Best Practices Every Backend Dev Should Know
Favicon
Building Type-Safe APIs: Integrating NestJS with Prisma and TypeScript
Favicon
The Secret Weapon Against API Abuse: The Power of Rate Limiting

Featured ones: