Login

Sign Up

Project Structuring & Error Handling
Daksh Dixit

Posted on Sep 16, 2025 | Backend

Project Structuring & Error Handling

Hey Devs!

As your backend applications grow from a single file to a more complex service, you will quickly find that keeping all your code in index.js is not a long-term solution. A disorganized codebase is hard to debug, difficult to expand, and a challenge for teams to work on.

Think of it like building a house. You don't just dump all the bricks, cement, and pipes in one big pile. You have separate plans for the foundation, the plumbing, the electrical wiring, and the rooms. A good project structure is the blueprint for your code, and good error handling is the safety system that prevents the house from falling apart when something unexpected happens.

Today, we will learn how to create a professional project structure and implement a smart error handling system for our backend applications.

Table of Contents
Why a Good Structure is a Must
A Scalable Project Structure in Detail
How the Pieces Work Together: A Request's Journey
Error Handling: From Basic to Professional
Level 1: The Basic Error Catcher
Level 2: The Smarter Error Manager
Level 3: The Professional Error System
Conclusion

Why a Good Structure is a Must

When you start, putting everything in one file is easy. But imagine your project grows to have 20, 50, or 100 different API endpoints. How will you find anything?

A good structure, based on the principle of Separation of Concerns, helps you by:

  • Making code easy to find: You know exactly where to look for routing logic, database logic, or request handling.
  • Improving maintainability: Fixing a bug or adding a feature becomes simpler because the relevant code is isolated.
  • Helping with teamwork: Multiple developers can work on different parts (like routes and controllers) without creating conflicts.

A Scalable Project Structure in Detail

Let's design a blueprint for our code that can handle growth. This structure is used in many professional projects.

/your-project
├── src/
│   ├── controllers/
│   │   └── user.controller.js  
│   ├── models/
│   │   └── user.model.js       
│   ├── routes/
│   │   └── user.routes.js            
│   ├── services/
│   │   └── user.service.js
│   ├── middlewares/
│   │   └── errorHandler.js     
│   └── utils/
│       └── helpers.js         
├── .env                        
├── .gitignore                  
├── index.js                    
└── package.json

Let's use an analogy of a large office building to understand each part.

  • src/routes/: The Reception Desk. This is the first point of contact. It defines all the URLs (endpoints) your application has, like /users or /login. When a request comes in, the router looks at the URL and directs the request to the correct department manager (the Controller).

  • src/controllers/: The Department Managers. A controller's job is to manage the request. It receives the request from the router, understands what needs to be done, but it doesn't do the hard work itself. Instead, it delegates the task to the specialists (the Services). Once the work is done, the controller takes the result and prepares a proper response to send back to the user.

  • src/services/: The Specialist Employees. This is where the main business logic happens. If the controller needs to get user data, it will ask the user service. The service will handle all the complex tasks, like talking to a database, performing calculations, or calling other APIs. Services do the actual "work".

  • src/models/: The File Blueprints. A model defines the structure of your data. For example, a user.model.js would define that a user must have a name, an email, and a password. It's like a template that ensures all your data is consistent and follows the rules before it's saved.

  • src/middlewares/: The Security Guards. Middleware is code that runs between the request arriving and the response being sent. Just like a security guard who checks your ID before letting you enter a building, middleware can check if a user is logged in, log request details, or, as we will see, catch errors.

  • src/utils/: The Office Supply Room. This folder holds common tools and helper functions that can be used by any part of the application. Things like a function to format dates or, in our case, a custom error class.

  • index.js: The Building's Main Entrance. This file is the starting point of your entire application. It sets up the Express server, connects all the routes, and applies global middleware.


How the Pieces Work Together: A Request's Journey

Let's trace a request for a user's details through our structure:

  1. A GET request arrives at http://yourapi.com/api/users/123.
  2. The main index.js file directs the /api/users part of the URL to the router in src/routes/user.routes.js.
  3. The user router sees the /123 part and calls the getUserById function from src/controllers/user.controller.js.
  4. The getUserById controller function doesn't search the database itself. It calls the findUser function from src/services/user.service.js, passing the ID 123.
  5. The findUser service function performs the database query to find the user.
  6. Once the user data is found, the service returns it to the controller.
  7. The controller formats a successful JSON response and sends it back to the client.

This organized flow keeps every part of your code clean and focused on a single responsibility.


Error Handling: From Basic to Professional

Now for the safety system. When something goes wrong, how do we handle it without crashing the server and without writing the same try...catch code in every single file?

Let’s think of it like managing a company. You don't want every employee to handle customer complaints in their own way. That would be chaos. You want a standard procedure where all problems are reported to a central complaints manager who handles them professionally. In Express, this "manager" is an error-handling middleware.

Level 1: The Basic Error Catcher (For Beginners)

Let's start simple. Our goal is to catch any error that happens anywhere in our app and send a generic "Something went wrong" message, so our server doesn't crash.

In your main index.js file, add this code at the very end, after all your routes.

// index.js

// ... all your other app.use() and route definitions go above this

// A simple route to test our error handler
app.get('/broken-page', (req, res, next) => {
    // We create a new error and pass it to next()
    const err = new Error('This page is intentionally broken.');
    next(err); 
});

// The Error Handling Middleware (Our Central Manager)
// It must have 4 arguments: err, req, res, next
app.use((err, req, res, next) => {
    console.error(err.stack); // For the developer to see the detailed error
    res.status(500).send('Oops! Something went wrong on our end.');
});

// ... your app.listen() goes here

How it works:

  • When you use next(err), Express skips all normal routes and looks for a middleware with four arguments.
  • Our middleware catches the err, logs its details for us (the developers), and sends a safe, user-friendly message. Your app stays alive!

Level 2: The Smarter Error Manager (For Intermediate Devs)

The basic handler is good, but it always sends a 500 status code, which means "Internal Server Error". What if the error was a "Not Found" (404) or a "Bad Request" (400)?

We can make our handler smarter by attaching a status code to our errors.

// In a controller or route file
app.get('/users/:id', (req, res, next) => {
    const user = findUserById(req.params.id); // Assume this function returns null if not found
    if (!user) {
        const err = new Error('User with this ID was not found.');
        err.statusCode = 404; // We add a custom property
        return next(err);
    }
    res.json(user);
});


// Update your error handling middleware in index.js
app.use((err, req, res, next) => {
    const statusCode = err.statusCode || 500;
    const message = err.message || 'Internal Server Error';

    console.error(`[ERROR] ${statusCode}: ${message}`);

    res.status(statusCode).json({
        success: false,
        message: message,
    });
});

Now our manager is more intelligent. It checks if the error has a specific statusCode. If it does, it uses it. If not, it defaults to 500. This gives you much more control over your API's responses.

Level 3: The Professional Error System (The Expert Way)

For large applications, creating errors like const err = new Error(...) and then adding properties can become repetitive. The professional way is to create a standard blueprint for all our application's errors using a custom class.

  1. Create the Blueprint (ApiError.js)

    In src/utils/, create a file ApiError.js.

    // src/utils/ApiError.js
    class ApiError extends Error {
      constructor(statusCode, message = 'Something went wrong') {
        super(message); // Call the parent Error constructor
        this.statusCode = statusCode;
        this.success = false;
    
        // This helps in capturing a clean stack trace
        Error.captureStackTrace(this, this.constructor);
      }
    }
    
    module.exports = ApiError;
    

    This class is a template. It makes creating structured errors very easy.

  2. Use the Blueprint in Your Code

    Now, throwing a detailed error is clean and simple.

    // In a controller file
    const ApiError = require('../utils/ApiError');
    
    // ... inside a function
    if (!user) {
        return next(new ApiError(404, 'User with this ID was not found.'));
    }
    

    This is much cleaner and ensures all your errors have a consistent structure. Your errorHandler middleware from Level 2 will work perfectly with this, no changes needed!


Conclusion

You have now learned the blueprint for building professional, scalable, and robust backend applications. A well-organized project structure makes development faster and easier, while a centralized error handler makes your application stable and predictable.

Start applying this structure to your new projects, and even consider refactoring old ones. It is a fundamental skill that will set you apart as a developer and make your code a pleasure to work with.

Have fun coding with Express!


Enjoy the content here?

Sign up on our platform or join our WhatsApp channel here to get more hands-on guides like this, delivered regularly.

See you in the next blog. Until then, keep practicing and happy learning!

1 Reactions

0 Bookmarks

Read next

Daksh Dixit

Daksh Dixit

Dec 29, 24

14 min read

|

Decoding the Web: A Beginner's Guide

Daksh Dixit

Daksh Dixit

Jan 5, 25

10 min read

|

Introduction to HTML, CSS, and JavaScript