# How to Structure a Node.js Project by Business Components for Better Maintainability

> Learn to structure your Node.js project by business components for enhanced maintainability. Organize code by domain to reduce coupling and enable independent scaling and deployment.

- Repository: [Yoni Goldberg/nodebestpractices](https://github.com/goldbergyoni/nodebestpractices)
- Tags: best-practices
- Published: 2026-02-26

---

**Structuring a Node.js project by business components involves organizing your codebase into self-contained folders representing distinct business domains (e.g., `orders`, `users`, `payments`) rather than technical layers (controllers, services, models), which dramatically reduces coupling and prepares your application for independent scaling and deployment.**

The `goldbergyoni/nodebestpractices` guide recommends abandoning the traditional "technical-role" folder layout—where controllers, services, and models live in separate top-level directories—in favor of a component-based architecture. When you structure a Node.js project by business components, each domain owns its entire stack from API routes to data access, creating clear boundaries that prevent the "spaghetti code" common in monolithic applications.

## Why Organize by Business Components?

Monolithic codebases organized by technical role quickly become unmaintainable. A single change to a user feature might require navigating [`controllers/user-controller.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/controllers/user-controller.js), [`services/user-service.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/services/user-service.js), and [`models/user-model.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/models/user-model.js), scattering domain logic across the filesystem. According to the Node.js Best Practices guide in [`sections/projectstructre/breakintcomponents.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/breakintcomponents.md), the solution is to "divide the whole stack into self-contained components" where other components consume functionality only through a public interface.

This architectural approach delivers specific, measurable benefits:

- **Clear ownership**: Small teams can own specific domains entirely, understanding the business rules without navigating unrelated code.
- **Reduced coupling**: Components expose only a public API; internal implementation files remain inaccessible to other components, preventing accidental cross-dependencies.
- **Scalable build and deployment**: Each component can be built, tested, and deployed independently, or later extracted into true microservices without structural rewrites.
- **Easier onboarding**: New developers need only understand the single component they modify, not the entire monolith.
- **Future-proofing**: The folder structure mirrors microservice architecture, making migration straightforward as the system grows.

## Component-Based vs. Technical-Role Directory Layout

The guide contrasts two fundamental approaches in [`sections/projectstructre/breakintcomponents.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/breakintcomponents.md). The **component-oriented** layout groups files by what they accomplish in the business domain, while the **technical-role** layout groups files by their architectural layer, scattering related logic across the project.

**Good: Component-Oriented Structure**

```

my-system
├─ apps (components)
│  ├─ orders
│  │  ├─ package.json
│  │  ├─ api
│  │  ├─ domain
│  │  └─ data-access
│  ├─ users
│  └─ payments
├─ libraries (cross-component utilities)
│  ├─ logger
│  └─ authenticator

```

**Bad: Technical-Role Grouping**

```

my-system
├─ controllers
│  ├─ user-controller.js
│  └─ order-controller.js
├─ services
│  ├─ user-service.js
│  └─ order-service.js
└─ models
   ├─ user-model.js
   └─ order-model.js

```

The component layout keeps all files belonging to a business feature together, reducing the risk of breaking changes and making the system's behavior easier to reason about.

## Practical Guidelines for Structuring Components

As detailed in [`sections/projectstructre/createlayers.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/createlayers.md), each business component should contain three distinct layers to separate concerns while maintaining cohesion:

1. **`api/`**: HTTP layer containing Express routes, GraphQL resolvers, or other entry points that handle request/response formatting.
2. **`domain/`**: Core business rules and use-case services implemented as plain JavaScript or TypeScript classes, free from framework dependencies.
3. **`data-access/`**: Repository or data-mapper patterns that handle database communication, ORM queries, and external API calls.

Only the **public interface**—typically an [`index.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/index.js) at the component root—should be imported by other components. This "export-only-public-API" pattern, referenced throughout the guide, ensures that internal refactoring never breaks downstream consumers.

## Code Example: Minimal Component Skeleton

The following structure demonstrates a properly isolated `orders` component from the `goldbergyoni/nodebestpractices` examples:

```

my-app/
├─ apps/
│  └─ orders/
│     ├─ api/
│     │  └─ routes.js
│     ├─ domain/
│     │  └─ createOrder.js
│     ├─ data-access/
│     │  └─ orderRepository.js
│     └─ index.js
└─ libraries/
   └─ logger/
      └─ index.js

```

**API Layer** ([`apps/orders/api/routes.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/apps/orders/api/routes.js)):

```javascript
import express from 'express';
import { createOrder } from '../domain/createOrder.js';

const router = express.Router();

router.post('/', async (req, res, next) => {
  try {
    const result = await createOrder(req.body);
    res.status(201).json(result);
  } catch (err) {
    next(err);
  }
});

export default router;

```

**Domain Layer** ([`apps/orders/domain/createOrder.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/apps/orders/domain/createOrder.js)):

```javascript
import { orderRepository } from '../data-access/orderRepository.js';

export async function createOrder(payload) {
  // Business validation and rules
  const order = { ...payload, status: 'new' };
  return orderRepository.save(order);
}

```

**Data Access Layer** ([`apps/orders/data-access/orderRepository.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/apps/orders/data-access/orderRepository.js)):

```javascript
import { db } from '../../libraries/logger/db.js';

export const orderRepository = {
  async save(order) {
    // Database persistence logic
    return { id: '123', ...order };
  },
};

```

**Public API Export** ([`apps/orders/index.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/apps/orders/index.js)):

```javascript
export { default as ordersRouter } from './api/routes.js';
export { createOrder } from './domain/createOrder.js';

```

Other components import only through this public surface:

```javascript
import { createOrder } from '../../apps/orders/index.js';

```

## When to Adopt This Structure

This architectural pattern delivers maximum value for **medium-to-large projects** exceeding approximately 5,000 lines of code, or any codebase expected to grow into multiple services. Teams that value **clear ownership boundaries** and **rapid developer onboarding** will benefit most from the enforced isolation that comes when you structure a Node.js project by business components.

For tiny scripts or single-purpose utilities, the overhead of multiple layers and strict export controls may be unnecessary. However, the pattern scales gracefully, making it safe to adopt early in projects with growth trajectories.

## Summary

- Structure a Node.js project by business components—such as `orders`, `users`, and `payments`—rather than technical layers like `controllers` and `models`.
- Each component in `apps/<component-name>/` contains its own `api/`, `domain/`, and `data-access/` layers as outlined in [`sections/projectstructre/breakintcomponents.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/breakintcomponents.md) and [`sections/projectstructre/createlayers.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/createlayers.md).
- Expose functionality solely through a public [`index.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/index.js) interface to prevent tight coupling between components.
- Internal implementation details remain private to the component, enabling refactoring without breaking changes.
- This pattern supports independent deployment, clear team ownership, and future migration to microservices without structural rewrites.

## Frequently Asked Questions

### What is the main advantage of structuring a Node.js project by business components?

The primary advantage is **reduced coupling** between domains. When you organize by business components, each domain (like `orders` or `users`) encapsulates its own API, business logic, and data access. Other components can only interact through the public interface defined in [`index.js`](https://github.com/goldbergyoni/nodebestpractices/blob/main/index.js), preventing the "spaghetti dependencies" that occur when technical layers span multiple business concerns.

### How does the component structure differ from the traditional MVC folder layout?

Traditional MVC layouts group files by their technical role—putting all controllers in `controllers/`, all services in `services/`, and all models in `models/`. This scatters a single business feature across multiple directories. In contrast, the component structure keeps all files related to a specific business domain (e.g., `orders`) together within a single folder, making the codebase easier to navigate and modify safely.

### Can I use this structure with a monorepo or microservices architecture?

Yes. The component-based structure is explicitly designed to support both monorepos and future microservice migrations. Each component can contain its own [`package.json`](https://github.com/goldbergyoni/nodebestpractices/blob/main/package.json) and be built independently. According to the guide in [`sections/projectstructre/breakintcomponents.md`](https://github.com/goldbergyoni/nodebestpractices/blob/main/sections/projectstructre/breakintcomponents.md), this structure "mirrors a micro-service architecture," allowing you to extract components into standalone services without restructuring the entire codebase.

### What should go in the `libraries/` folder versus the `apps/` folder?

The `apps/` folder contains business components that represent specific domains like `orders` or `payments`. The `libraries/` folder contains **cross-cutting concerns**—utilities like loggers, authentication middleware, or database connection pools that multiple components share but do not themselves represent business domains. These libraries should remain stateless and framework-agnostic where possible.