This content originally appeared on DEV Community and was authored by Josiah Victor
Table of Contents
-
Mastering Backend Node.js Folder Structure: A Beginner’s Guide
- Table of Contents
- Introduction
- Why a Good Folder Structure Matters
- The Basic Folder Structure for Beginners
- Overview of the Structure
- Step-by-Step Setup:
-
Breaking Down the Folders:
- controllers/
- models/
- routes/
- middlewares/
- utils/
- config/
- server.js
- Advanced Folder Structure for Scalability
-
Breakdown of Additional Folders
- services/
- tests/
- public/
- app.js
- Why This Structure Works for Scalability
- Best Practices for Organizing Your Code
- Conclusion
- Wrap-Up and Feedback 💬
- Stay Connected 🌐
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
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
Create Your Folders:
Use these commands to set up the structure:
mkdir controllers models routes middlewares utils config
touch server.js
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:
- Receive the request.
- Fetch the user data from the database using the model.
- 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' });
}
};
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);
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;
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
};
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');
};
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;
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}`));
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
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);
};
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 });
};
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 forcontrollers/userController.js
might be intests/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);
});
});
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
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
Why This Structure Works for Scalability
Separation of Concerns:
Each folder has a specific responsibility, making the codebase easier to understand and maintain.Reusability:
The services folder keeps your business logic reusable and avoids duplication.Testability:
A clear structure ensures every part of your app is easy to test, leading to fewer bugs.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
- Start Small, Scale Later: Begin with a simple structure and refactor as your app grows.
- Stick to Conventions: Use clear and consistent naming.
- Stay DRY (Don’t Repeat Yourself): Extract repetitive logic into reusable utilities.
- Use Environment Variables: Keep sensitive data like API keys out of your codebase.
- Document Everything: Include a README with explanations for your setup.
- 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! 🚀
This content originally appeared on DEV Community and was authored by Josiah Victor
Josiah Victor | Sciencx (2025-01-12T20:05:07+00:00) Mastering Backend Node.js Folder Structure, A Beginner’s Guide. Retrieved from https://www.scien.cx/2025/01/12/mastering-backend-node-js-folder-structure-a-beginners-guide/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.