
Folder Architecture at Scale: How to Organize React, Angular, and Node.js Projects with SOLID and Clean Architecture
If you have ever had to add a simple field to a form and found yourself navigating through five folders in the project root (/components, /services, /types, /hooks, /utils), you have been a victim of file-type architecture.
At the beginning of a software project, grouping files by what they are (components with components, services with services) seems clean and intuitive. However, as the application scales to hundreds of screens and business rules, this approach turns into a cognitive nightmare. Coupling skyrockets, cohesion goes out the window, and deleting an old feature without breaking the system becomes an impossible mission.
Today, architectural consensus among high-performance teams in React, Angular, and Node.js revolves around Vertical Slices and Domain/Feature-Scoped Organization.
In this article, we will dissect how to structure folders for modern applications across the frontend and backend, demonstrating how the principles of SOLID, DRY, and Separation of Concerns (SoC) shape this architecture in practice.
1. The Golden Principle: Colocation and Cohesion
Before looking at specific frameworks, we need to understand the fundamental rule governing modern architectures: code that changes together should live together.
This is what we call Colocation. If the screen component for a shopping cart needs a freight calculation hook, a specific TypeScript interface, and an API call, all of these files should live inside the Cart feature folder, rather than being scattered globally across the project.
The Architectural Acid Test: If your Product Owner asked you to remove the "Wishlist" feature today, could you simply delete the
/features/wishlistfolder and have the project compile without errors? If the answer is no, your coupling is too high.
2. The Theoretical Foundation: SOLID and DRY in the File System
A good folder structure is the physical manifestation of software engineering principles in your repository. Here is how they apply to our organization:
SOLID in Folder Practice
- S β Single Responsibility Principle (SRP): In the legacy model, a
UserControllerorGlobalServicesfile accumulated business rules, HTTP validation, and database queries. In our structure, we isolate responsibilities. In Node.js, a file inuse-cases/create-order.usecase.tshas only one reason to change: the order business rule. It ignores the HTTP protocol (thecontroller's responsibility) and the database (therepository's responsibility). - O β Open/Closed Principle (OCP): Your system should be open for extension but closed for modification. If you need to add a new payment method in the frontend, you do not edit an existing massive component; you simply create a new folder inside
features/billing/components/(e.g.,/pix-checkout/) and export it via the feature contract. The existing code remains untouched. - L & I β Liskov Substitution & Interface Segregation: We avoid the global
/typesfolder containing a 3,000-lineindex.tsfile. By placing thetypes/ordto/folder inside each specific module, we ensure that a component consumes only the strict contracts necessary for its functionality, without relying on type definitions from other areas of the system. - D β Dependency Inversion Principle (DIP): Our business rules depend on abstractions (interfaces), not concrete implementations. This allows us to swap external libraries or databases by simply replacing the file in the infrastructure layer, without rewriting the domain logic.
Strategic DRY (And the Coupling Trap)
The Don't Repeat Yourself (DRY) principle is often applied blindly by developers. In our architecture, it is reserved for pure infrastructure and generic UI (/shared, /components/ui, /lib). Buttons, modals, and HTTP clients are written only once.
However, we must exercise extreme caution with DRY when applied to business rules. If the OrderDTO entity in the Orders module has the exact same four fields as the InvoiceDTO in the Billing module, do not try to unify them into a BaseGlobalDTO in /shared.
Accidental duplication across different domains is infinitely better than premature coupling. If the billing department requires a new tax field tomorrow, the orders module should not be impacted.
3. Angular: The Standalone Era and Practical DDD
With the end of NgModules and the consolidation of Standalone APIs, Angular was freed from bureaucratic complexity. The modern pattern draws heavy inspiration from Nx monorepo conventions and lightweight Domain-Driven Design (DDD) concepts.
We divide the application into four macro-categories: core, shared (or ui), features, and data-access.
Recommended Structure for Angular
src/
ββ app/
β ββ core/ # Singleton services, interceptors, and global guards
β β ββ auth/
β β β ββ auth.guard.ts
β β β ββ auth.service.ts
β β ββ http/
β β ββ error.interceptor.ts
β β
β ββ shared/ # "Dumb" and generic UI (Design System)
β β ββ ui/
β β β ββ button/
β β β ββ modal/
β β ββ utils/
β β
β ββ features/ # Business slices (Vertical Slices)
β β ββ checkout/
β β β ββ components/ # Internal components exclusive to checkout
β β β ββ services/ # Checkout presentation logic
β β β ββ checkout.routes.ts # Lazy Loaded route
β β β ββ checkout.component.ts
β β β
β β ββ catalog/
β β
β ββ data-access/ # External API communication and Global State (Signals)
β ββ products/
β β ββ products.api.ts
β β ββ products.store.ts # SignalStore / State
β β ββ products.types.ts
β ββ user/
- Separation of Concerns (SoC): The
data-accessfolder centralizes HTTP calls and state management (SignalStore). Thefeatures/layer consumes this data without knowing how it was fetched or tagged in the cache. - UI Isolation: Anything inside
features/checkout/componentsis private. Iffeatures/catalogneeds the same product card, that component must be refactored and promoted toshared/ui.
4. React: Feature-Scoped Architecture and Barrel Files
In the React ecosystem (whether Next.js with App Router or SPAs with Vite), architectural freedom often leads to traumatic refactoring. The most resilient structure adopts the Bulletproof React methodology, abandoning bloated global folders in favor of self-sufficient modules.
Recommended Structure for React
src/
ββ app/ # Routing (Next.js App Router or React Router setup)
β ββ (public)/
β ββ (authenticated)/
β ββ dashboard/
β ββ page.tsx # The page acts purely as a Feature orchestrator!
β
ββ components/ # Global UI components (Shadcn / Tailwind)
β ββ ui/
β β ββ button.tsx
β β ββ dialog.tsx
β ββ layouts/
β
ββ lib/ # External client configurations (Axios, QueryClient)
β ββ axios.ts
β ββ react-query.ts
β
ββ features/ # The core of the application
β ββ billing/
β β ββ api/ # TanStack Query hooks (useInvoice, usePay)
β β ββ components/ # InvoiceList, PaymentModal
β β ββ hooks/ # Local feature hooks (useTaxCalculator)
β β ββ types/ # DTO interfaces restricted to billing
β β ββ index.ts # BARREL FILE: Exports only what is public!
β β
β ββ projects/
β
ββ stores/ # Global state (Zustand, when strictly necessary)
ββ use-theme-store.ts
The Power of the Barrel File (index.ts)
Inside each React feature, we maintain an index.ts file at the root acting as the module's public API. If the Dashboard page needs to render the invoice list, it imports:
import { InvoiceList } from '@/features/billing';
The page is physically prevented from importing deep internal files (such as a private useTaxCalculator hook), maintaining encapsulation and the information hiding principle.
5. Node.js (NestJS / Express): The Modular Monolith
In the backend, the fundamental mistake is structuring the project by technical layers at the root (/controllers, /models, /repositories). This violates the Single Responsibility Principle at the module level.
Whether using NestJS (which natively guides us down this path) or a clean setup with Express/Fastify, we must design the backend as a Modular Monolith based on Clean Architecture.
Recommended Structure for Node.js
src/
ββ config/ # Environment variables, DB connection, loggers
ββ shared/ # Global middlewares, custom exceptions, decorators
β ββ errors/
β ββ middlewares/
β
ββ modules/ # Business domains (Bounded Contexts)
β ββ orders/
β β ββ dto/ # Data Transfer Objects (Zod / Class-Validator)
β β β ββ create-order.dto.ts
β β β ββ order-response.dto.ts
β β β
β β ββ entities/ # Domain Models or ORM Schemas
β β β ββ order.entity.ts
β β β
β β ββ repositories/ # Database isolation (Repository Pattern)
β β β ββ order.repository.interface.ts
β β β ββ prisma-order.repository.ts
β β β
β β ββ use-cases/ # Pure business logic (Or Services in NestJS)
β β β ββ create-order.usecase.ts
β β β ββ calculate-discount.usecase.ts
β β β
β β ββ orders.controller.ts # HTTP Layer / Data Input & Output
β β ββ orders.module.ts # Dependency wiring (Dependency Injection)
β β
β ββ users/
β
ββ main.ts # Application entrypoint
Strategic Advantages of the Modular Monolith
- Infrastructure Independence (DIP): By isolating
use-cases(business logic) fromrepositories(data access), if the company decides to migrate from MongoDB to PostgreSQL in the future, you will only change the files inside/repositories. The business logic remains untouched. - Microservices-Ready: If the
ordersmodule experiences a severe processing bottleneck and needs to scale independently, boundaries are already drawn. Extracting it into an independent microservice within a Docker container becomes a cut-and-paste task, not a complete rewrite.
Conclusion
Software architecture does not exist to bureaucratize engineering work with rigid rules; its main function is cognitive load management.
When we organize React, Angular, or Node.js projects by Features and implement SOLID and DRY principles at the root of our directory structure, we enable any developer to understand, modify, and test a specific slice of the system without needing to load the entire application into their short-term mental memory.
The next time you start a project or refactor a legacy system, abandon grouping by syntax or file type. Start structuring your code by the business problems it solves.
Comments
Loading comments...
Join the conversation
Sign in with your account to comment on this article.