Pular para o conteúdo
< samuelsantana.dev />
Voltar para o Blog

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

Samuel Santana
Publicado em 24 de julho de 2026(Editado em 25 de julho de 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.

Comentários

Carregando comentários...

Participe da conversa

Entre com sua conta para comentar neste artigo.