Arquitetura em camadas com Node.js, Fastify, Prisma e Zod
- #Node.js
- #Arquitetura de Sistemas
Introdução
Organizar bem a estrutura de um projeto backend faz toda a diferença na hora de dar manutenção, adicionar novas funcionalidades e trabalhar em equipe.
Neste artigo, vou mostrar como organizei a arquitetura da minha API de Metas de Investimento em camadas, usando Fastify, Prisma e Zod. A ideia é separar bem as responsabilidades para deixar o código mais limpo, testável e fácil de escalar.
Visão geral da arquitetura e estrutura de pastas
O projeto segue uma arquitetura em camadas, separando responsabilidades da seguinte forma, refletida diretamente na estrutura de pastas dentro de src/:
src/
├── controllers/
├── entities/
├── repositories/
├── routes/
├── schemas/
├── services/
├── app.ts
└── server.ts
- Routes → Organizam as rotas HTTP no Fastify.
- Controllers → Recebem as requisições e chamam os serviços.
- Services → Contêm a lógica de negócio e validações.
- Repositories → Isolam o acesso ao banco de dados (Prisma Client).
- Entities → Representam as estruturas de domínio manipuladas pela aplicação.
- Schemas (Zod) → Validam entradas/saídas e expõem tipos para as camadas.
Essa separação ajuda a evitar um “bolo de código” nas rotas e deixa claro onde cada tipo de lógica deve ficar. Os arquivos app.ts e server.ts são responsáveis pela inicialização da aplicação e do servidor Fastify, respectivamente.
Routes: ponto de entrada da requisição
A camada de Routes é responsável por registrar as rotas HTTP no Fastify e direcionar a requisição para o controller correto.
O foco aqui é apenas:
- definir o método HTTP (GET, POST, PUT, DELETE, etc.)
- definir o caminho (
/goals,/goals/:id, etc.) - chamar o controller correspondente
Por exemplo, para as rotas de metas de investimento, temos:
POST /investment-goals(Criar meta)GET /investment-goals?name=...&month=...&page=1&pageSize=20(Listar metas)GET /investment-goals/:id(Buscar meta por ID)PUT /investment-goals/:id(Atualizar meta por ID)DELETE /investment-goals/:id(Excluir meta por ID)
Nada de lógica de negócio dentro das rotas – isso fica para o service.
Controllers: conversando com o mundo externo
Os Controllers recebem a requisição já roteada e fazem o papel de “ponte” entre o mundo externo (HTTP) e o domínio da aplicação.
Responsabilidades principais:
- extrair dados do
request(params, query, body) - chamar o service adequado
- transformar o resultado em uma resposta HTTP
Eles também podem lidar com alguns detalhes de status code (por exemplo, retornar 201 Created após um cadastro). Um exemplo é o IndexController que retorna informações de diagnóstico na rota /.
Services: onde vive a regra de negócio
A camada de Services é o coração da aplicação. Aqui ficam:
- regras de negócio (por exemplo: validar metas de investimento, limites, datas, etc.)
- orquestração entre repositórios
- chamadas para outras APIs internas, se existirem
O service não sabe nada sobre HTTP diretamente (não conhece request/response). Ele trabalha apenas com dados e modelos de domínio, o que facilita muito na hora de testar.
Repositories: acesso ao banco de dados
Os Repositories isolam toda a parte de acesso ao banco de dados usando o Prisma Client.
Vantagens:
- Se um dia você mudar de banco ou de ORM, a maior parte do impacto fica só nessa camada.
- O service não precisa saber detalhes de SQL/Prisma, apenas chama métodos como
create,findById,listByUser, etc.
Entities: o domínio da aplicação
As Entities representam os objetos principais do seu domínio.
No exemplo da API de metas de investimento, poderíamos ter algo como:
InvestmentGoalUserTransaction
Essas entidades ajudam a dar nome e forma aos conceitos do negócio, deixando o código mais expressivo e próximo da linguagem do dia a dia.
Schemas (Zod): validação e tipos
Por fim, os Schemas com Zod são usados para:
- validar dados de entrada (body, params, query)
- garantir o formato de saída
- gerar tipos TypeScript a partir dos schemas
Isso reduz erros de runtime e traz mais segurança para o fluxo inteiro, desde o controller até o service.
Benefícios dessa arquitetura
Organizar o projeto dessa forma traz alguns benefícios práticos:
- Manutenção mais simples: fica fácil localizar onde mexer quando surge uma nova regra de negócio.
- Testes mais fáceis: services e repositories podem ser testados de forma isolada.
- Escalabilidade: novas funcionalidades podem ser adicionadas reaproveitando estruturas já existentes.
- Código mais legível para o time: cada camada tem um papel bem definido.
Conclusão
Arquitetura não precisa ser algo super complexo. Com algumas camadas bem definidas — Routes, Controllers, Services, Repositories, Entities e Schemas (Zod) — e uma estrutura de pastas clara, já é possível sair do caos e ter um backend muito mais organizado.
Se você está começando um novo projeto ou quer refatorar um que já existe, vale testar esse modelo de organização e ir adaptando conforme a necessidade do seu domínio.
Se quiser explorar o código dessa arquitetura na prática, o repositório está aqui:



