Jefferson Lima
Jefferson Lima12/12/2025 22:34
Compartilhe

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:

  • InvestmentGoal
  • User
  • Transaction

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:

https://github.com/JeffersonISLima/investment-goals-api

Compartilhe
Comentários (0)