Skip to content
< samuelsantana.dev />
Back to the Blog

Arquitetura de Pastas em Escala: Como organizar projetos React, Angular e Node.js com SOLID e Clean Architecture

You're reading the Portuguese version of this article. Read it in English

Samuel Santana
Published on July 24, 2026(Edited on July 25, 2026)
AngularReactNodeJS

Se você já precisou adicionar um simples campo em um formulário e teve que navegar por cinco pastas na raiz do projeto (/components, /services, /types, /hooks, /utils), você foi vítima da arquitetura por tipo de arquivo.

No início de um software, agrupar arquivos pelo que eles são (componentes com componentes, serviços com serviços) parece limpo e intuitivo. Mas, à medida que a aplicação escala para centenas de telas e regras de negócio, essa abordagem se transforma em um pesadelo cognitivo. O acoplamento dispara, a coesão vai para o espaço e deletar uma funcionalidade antiga sem quebrar o sistema vira uma missão impossível.

Hoje, o consenso arquitetural entre equipes de alta performance em React, Angular e Node.js gira em torno de Vertical Slices (Fatias Verticais) e Organização por Domínio/Feature.

Neste artigo, vamos dissecar como estruturar pastas para aplicações modernas no frontend e no backend, demonstrando como os princípios de SOLID, DRY e Separation of Concerns (SoC) moldam essa arquitetura na prática.


1. O Princípio de Ouro: Colocation e Coesão

Antes de olharmos para os frameworks, precisamos entender a regra fundamental que rege as arquiteturas modernas: o código que muda junto deve morar junto.

Isso é o que chamamos de Colocation (Colocação). Se o componente de tela de um carrinho de compras precisa de um hook para calcular frete, uma interface TypeScript específica e uma chamada de API, todos esses arquivos devem viver dentro da pasta da feature do Carrinho, e não espalhados globalmente pelo projeto.

O Teste de Ácido da Arquitetura: Se o seu Product Owner pedisse para remover a funcionalidade de "Lista de Desejos" hoje, você conseguiria simplesmente deletar a pasta /features/wishlist e o projeto continuaria compilando sem erros? Se a resposta for não, seu acoplamento está alto demais.


2. A Fundação Teórica: SOLID e DRY no Sistema de Arquivos

Uma boa estrutura de pastas é a manifestação física dos princípios de engenharia de software no seu repositório. Veja como eles se aplicam à nossa organização:

O SOLID na Prática das Pastas

  • S — Single Responsibility Principle (SRP): No modelo legado, um arquivo UserController ou GlobalServices acumulava regras de negócio, validação HTTP e chamadas de banco. Na nossa estrutura, isolamos a responsabilidade. No Node.js, um arquivo em use-cases/create-order.usecase.ts tem apenas uma razão para mudar: a regra de negócio do pedido. Ele ignora o protocolo HTTP (responsabilidade do controller) e o banco de dados (responsabilidade do repository).
  • O — Open/Closed Principle (OCP): Seu sistema deve estar aberto para expansão, mas fechado para modificação. Se precisar adicionar um novo método de pagamento no frontend, você não edita um componente gigante existente; você simplesmente cria uma nova pasta dentro de features/billing/components/ (ex: /pix-checkout/) e exporta pelo contrato da feature. O código antigo permanece intocável.
  • L & I — Liskov Substitution & Interface Segregation: Evitamos a pasta global /types com um arquivo index.ts de 3.000 linhas. Ao colocar a pasta types/ ou dto/ dentro de cada módulo específico, garantimos que um componente consuma apenas os contratos estritamente necessários para seu funcionamento, sem depender de tipagens de outras áreas do sistema.
  • D — Dependency Inversion Principle (DIP): Nossas regras de negócio dependem de abstrações (interfaces), não de implementações concretas. Isso nos permite trocar bibliotecas externas ou bancos de dados apenas substituindo o arquivo na camada de infraestrutura, sem reescrever a lógica do domínio.

O DRY Estratégico (E a Armadilha do Acoplamento)

O princípio Don't Repeat Yourself (DRY) é aplicado às cegas por muitos desenvolvedores. Na nossa arquitetura, ele é utilizado para infraestrutura pura e UI genérica (/shared, /components/ui, /lib). Botões, modais e clientes HTTP são escritos uma única vez.

Porém, devemos ter um cuidado extremo com o DRY nas regras de negócio. Se a entidade OrderDTO no módulo de Pedidos tem os mesmos quatro campos que a InvoiceDTO no módulo de Faturamento, não tente unificá-las em um BaseGlobalDTO em /shared.

Duplicação acidental entre domínios diferentes é infinitamente melhor do que o acoplamento prematuro. Se o setor fiscal exigir um novo campo na fatura amanhã, o módulo de pedidos não deve ser impactado.


3. Angular: A Era Standalone e DDD Prático

Com o fim dos NgModules e a consolidação das APIs Standalone, o Angular foi libertado da complexidade burocrática. O padrão moderno se inspira nas convenções de monorepos da Nx e em conceitos leves de Domain-Driven Design (DDD).

Dividimos a aplicação em quatro macro-categorias: core, shared (ou ui), features e data-access.

Estrutura Recomendada para Angular

src/
 ├─ app/
 │   ├─ core/                     # Singleton services, interceptors e guards globais
 │   │   ├─ auth/
 │   │   │   ├─ auth.guard.ts
 │   │   │   └─ auth.service.ts
 │   │   └─ http/
 │   │       └─ error.interceptor.ts
 │   │
 │   ├─ shared/                   # UI "burra" e genérica (Design System)
 │   │   ├─ ui/
 │   │   │   ├─ button/
 │   │   │   └─ modal/
 │   │   └─ utils/
 │   │
 │   ├─ features/                 # As fatias de negócio (Vertical Slices)
 │   │   ├─ checkout/
 │   │   │   ├─ components/       # Componentes internos e exclusivos do checkout
 │   │   │   ├─ services/         # Lógica de apresentação do checkout
 │   │   │   ├─ checkout.routes.ts # Rota com Lazy Loading
 │   │   │   └─ checkout.component.ts
 │   │   │
 │   │   └─ catalog/
 │   │
 │   └─ data-access/              # Comunicação com APIs externas e Estado Global (Signals)
 │       ├─ products/
 │       │   ├─ products.api.ts
 │       │   ├─ products.store.ts # SignalStore / Estado
 │       │   └─ products.types.ts
 │       └─ user/
  • Separation of Concerns (SoC): A pasta data-access centraliza chamadas HTTP e gerenciamento de estado (SignalStore). A camada de features/ consome esses dados sem saber como foram buscados ou tagueados no cache.
  • Isolamento de UI: O que está dentro de features/checkout/components é privado. Se features/catalog precisar do mesmo card de produto, esse componente é refatorado e promovido para shared/ui.

4. React: Feature-Scoped Architecture e Barrel Files

No ecossistema React (seja Next.js com App Router ou SPAs com Vite), a liberdade arquitetural costuma gerar refatorações traumáticas. A estrutura mais resiliente adota a metodologia Bulletproof React, abandonando pastas globais inchadas em favor de módulos auto-suficientes.

Estrutura Recomendada para React

src/
 ├─ app/                          # Rotas (Next.js App Router ou React Router setup)
 │   ├─ (public)/
 │   └─ (authenticated)/
 │       └─ dashboard/
 │           └─ page.tsx          # A page atua apenas como orquestradora da Feature!
 │
 ├─ components/                   # Componentes globais de UI (Shadcn / Tailwind)
 │   ├─ ui/
 │   │   ├─ button.tsx
 │   │   └─ dialog.tsx
 │   └─ layouts/
 │
 ├─ lib/                          # Configurações de clientes externos (Axios, QueryClient)
 │   ├─ axios.ts
 │   └─ react-query.ts
 │
 ├─ features/                     # O coração da aplicação
 │   ├─ billing/
 │   │   ├─ api/                  # Hooks do TanStack Query (useInvoice, usePay)
 │   │   ├─ components/           # InvoiceList, PaymentModal
 │   │   ├─ hooks/                # Hooks locais da feature (useTaxCalculator)
 │   │   ├─ types/                # Interfaces DTOs restritas ao billing
 │   │   └─ index.ts              # BARREL FILE: Exporta apenas o que é público!
 │   │
 │   └─ projects/
 │
 └─ stores/                       # Estado global (Zustand, quando estritamente necessário)
     └─ use-theme-store.ts

O Poder do Barrel File (index.ts)

Dentro de cada feature React, mantemos um arquivo index.ts na raiz atuando como uma API pública do módulo. Se a página de Dashboard precisa renderizar a lista de faturas, ela importa:

import { InvoiceList } from '@/features/billing';

A página fica fisicamente impedida de importar arquivos internos profundos (como um hook privado useTaxCalculator), mantendo o encapsulamento e o princípio de ocultação de informação.


5. Node.js (NestJS / Express): O Monólito Modular

No backend, o erro fundamental é estruturar o projeto por camadas técnicas na raiz (/controllers, /models, /repositories). Isso viola o Princípio de Responsabilidade Única em nível de módulo.

Seja utilizando o NestJS (que nos direciona nativamente para esse caminho) ou um setup limpo com Express/Fastify, devemos projetar o backend como um Monólito Modular baseado em Clean Architecture.

Estrutura Recomendada para Node.js

src/
 ├─ config/                       # Variáveis de ambiente, conexão de DB, loggers
 ├─ shared/                       # Middlewares globais, exceções customizadas, decorators
 │   ├─ errors/
 │   └─ middlewares/
 │
 ├─ modules/                      # Os domínios de negócio (Bounded Contexts)
 │   ├─ orders/
 │   │   ├─ dto/                  # Data Transfer Objects (Zod / Class-Validator)
 │   │   │   ├─ create-order.dto.ts
 │   │   │   └─ order-response.dto.ts
 │   │   │
 │   │   ├─ entities/             # Modelos de Domínio ou Schemas do ORM
 │   │   │   └─ order.entity.ts
 │   │   │
 │   │   ├─ repositories/         # Isolamento do banco de dados (Repository Pattern)
 │   │   │   ├─ order.repository.interface.ts
 │   │   │   └─ prisma-order.repository.ts
 │   │   │
 │   │   ├─ use-cases/            # A lógica de negócio pura (Ou Services no NestJS)
 │   │   │   ├─ create-order.usecase.ts
 │   │   │   └─ calculate-discount.usecase.ts
 │   │   │
 │   │   ├─ orders.controller.ts  # Camada HTTP / Entrada e saída de dados
 │   │   └─ orders.module.ts      # Fiação de dependências (Injeção de Dependência)
 │   │
 │   └─ users/
 │
 └─ main.ts                       # Entrypoint da aplicação

Vantagens Estratégicas do Monólito Modular

  • Independência de Infraestrutura (DIP): Ao isolar os use-cases (regra de negócio) dos repositories (acesso a dados), se a empresa decidir migrar do MongoDB para o PostgreSQL no futuro, você alterará apenas os arquivos dentro de /repositories. A regra de negócio permanece intocada.
  • Preparado para Microserviços: Se o módulo de orders passar por um gargalo severo de processamento e precisar escalar de forma isolada, ele já está delimitado. Extraí-lo para um microserviço independente em um container Docker será uma tarefa de recorte e colagem, não uma reescrita completa.

Conclusão

A arquitetura de software não existe para burocratizar o trabalho da engenharia com regras engessadas; sua principal função é a gestão da carga cognitiva.

Quando organizamos projetos em React, Angular ou Node.js por Features e implementamos os princípios SOLID e DRY na raiz da nossa estrutura, permitimos que qualquer desenvolvedor consiga entender, modificar e testar uma fatia específica do sistema sem precisar carregar a aplicação inteira em sua memória de curto prazo.

Da próxima vez que iniciar um projeto ou refatorar um legado, abandone o agrupamento por sintaxe ou tipo de arquivo. Comece a estruturar seu código pelos problemas de negócio que ele resolve.

Comments

Loading comments...