How to Structure a Node.js Project by Business Components for Better Maintainability
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, services/user-service.js, and models/user-model.js, scattering domain logic across the filesystem. According to the Node.js Best Practices guide in 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. 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, each business component should contain three distinct layers to separate concerns while maintaining cohesion:
api/: HTTP layer containing Express routes, GraphQL resolvers, or other entry points that handle request/response formatting.domain/: Core business rules and use-case services implemented as plain JavaScript or TypeScript classes, free from framework dependencies.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 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):
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):
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):
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):
export { default as ordersRouter } from './api/routes.js';
export { createOrder } from './domain/createOrder.js';
Other components import only through this public surface:
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, andpayments—rather than technical layers likecontrollersandmodels. - Each component in
apps/<component-name>/contains its ownapi/,domain/, anddata-access/layers as outlined insections/projectstructre/breakintcomponents.mdandsections/projectstructre/createlayers.md. - Expose functionality solely through a public
index.jsinterface 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, 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 and be built independently. According to the guide in 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →