
Arquitetura de Pastas em Escala: Como organizar projetos React, Angular e Node.js com SOLID e Clean Architecture
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/wishliste 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
UserControllerouGlobalServicesacumulava regras de negócio, validação HTTP e chamadas de banco. Na nossa estrutura, isolamos a responsabilidade. No Node.js, um arquivo emuse-cases/create-order.usecase.tstem apenas uma razão para mudar: a regra de negócio do pedido. Ele ignora o protocolo HTTP (responsabilidade docontroller) e o banco de dados (responsabilidade dorepository). - 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
/typescom um arquivoindex.tsde 3.000 linhas. Ao colocar a pastatypes/oudto/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-accesscentraliza chamadas HTTP e gerenciamento de estado (SignalStore). A camada defeatures/consome esses dados sem saber como foram buscados ou tagueados no cache. - Isolamento de UI: O que está dentro de
features/checkout/componentsé privado. Sefeatures/catalogprecisar do mesmo card de produto, esse componente é refatorado e promovido parashared/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) dosrepositories(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
orderspassar 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.
Comentários
Carregando comentários...
Participe da conversa
Entre com sua conta para comentar neste artigo.