# Estruturar Node.js produção: guia prático para projetos robustos

> Estruturar Node.js produção exige organização modular com separação de responsabilidades, configuração por ambiente e tratamento de erros robusto. A estrutura deve incluir pastas para rotas, controladores, modelos, serviços e middlewares, além de arquivos de configuração (.env, config/) e logs centralizados. Essa abordagem garante escalabilidade, manutenibilidade e resiliência do sistema em ambiente produtivo.

*Blog Sem Juízo · Novidades · 08 de julho de 2026 · Zeca Maranhão*

Organizar um projeto Node.js para produção vai além de criar pastas bonitinhas. Neste guia, você monta uma estrutura que escala, com separação de responsabilidades, configuração por ambiente e tratamento de erros que não quebra o sistema.

Você criou um projeto Node.js que funciona lindamente na sua máquina. Subiu para o servidor e, de repente, tudo quebra: variáveis de ambiente somem, erros aparecem sem rastro, o código vira um novelo. O problema raramente é o Node. É a estrutura.

Estruturar um projeto Node.js para produção significa organizar o código de forma que ele seja previsível, testável e fácil de depurar. Não importa se o projeto tem 10 ou 10 mil arquivos: as mesmas regras se aplicam. Abaixo, um passo a passo que você pode aplicar hoje.

## Pré-requisitos

Antes de começar, tenha Node.js 18+ instalado e um editor de código. Conhecimento básico de Express ou framework similar ajuda, mas não é obrigatório. O guia é agnóstico de framework, os princípios servem para qualquer stack.

## Passo 1: Defina a estrutura de pastas por domínio, não por tipo

O erro clássico é criar pastas controllers/, models/, routes/ e jogar tudo lá. Isso funciona em projetos pequenos, mas quando o sistema cresce, você passa mais tempo rolando arquivos do que codando.

Organize por domínio (ou módulo). Exemplo:

project/ ├── src/ │ ├── modules/ │ │ ├── user/ │ │ │ ├── user.controller.ts │ │ │ ├── user.service.ts │ │ │ ├── user.repository.ts │ │ │ └── user.routes.ts │ │ └── order/ │ │ ├── order.controller.ts │ │ └── ... │ ├── shared/ │ │ ├── middlewares/ │ │ ├── errors/ │ │ └── utils/ │ ├── config/ │ │ └── database.ts │ └── app.ts ├── tests/ ├── .env.example └── package.json

**Dica:** Cada módulo deve ser independente. Se você apagar a pasta user/, o sistema não quebra, a menos que outro módulo dependa dele. Isso força um acoplamento mínimo.

**Erro comum:** Criar pastas helpers/ ou utils/ genéricas que viram depósitos de funções soltas. Se uma função não se encaixa em nenhum módulo, talvez ela deva estar em um módulo novo.

## Passo 2: Separe a configuração do ambiente do código

Nunca coloque chaves de API, senhas de banco ou URLs diretamente no código. Use variáveis de ambiente com um arquivo .env e um schema validator.

Instale dotenv e crie um arquivo .env.example no repositório (sem valores reais) para documentar o que é esperado.

## .env.example

PORT=3000 DATABASE_URL=postgres://user:pass@localhost:5432/db JWT_SECRET=change_me

No código, carregue as variáveis no início e valide com zod ou joi:

import 'dotenv/config'; import { z } from 'zod';

const envSchema = z.object({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), });

export const env = envSchema.parse(process.env);

**Dica:** Use z.coerce.number() para converter strings de ambiente em números sem quebrar.

**Erro comum:** Esquecer de validar. Um DATABASE_URL ausente pode gerar erro 500 em vez de uma mensagem clara na inicialização.

## Passo 3: Centralize o tratamento de erros

Sem um handler central, cada erro vira um console.log perdido. Crie um middleware de erro no Express (ou equivalente no seu framework) que capture todas as exceções.

// shared/errors/AppError.ts export class AppError extends Error { public readonly statusCode: number; public readonly isOperational: boolean;

constructor(message: string, statusCode = 400) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); } }

No middleware global:

app.use((err: Error, req: Request, res: Response, next: NextFunction) => { if (err instanceof AppError) { return res.status(err.statusCode).json({ error: err.message }); } // Erro não esperado: logar e retornar 500 genérico logger.error(err); return res.status(500).json({ error: 'Erro interno do servidor' }); });

**Dica:** Erros operacionais (esperados, como validação) devem retornar mensagens amigáveis. Erros de programação (tipo undefined) devem ser logados e escondidos do cliente.

**Erro comum:** Misturar erros operacionais com de programação. Um TypeError não deve virar um AppError com status 400.

## Passo 4: Adote um logger estruturado

console.log não serve para produção. Use pino ou winston com saída JSON. Isso permite que ferramentas como Datadog ou ELK interpretem os logs.

import pino from 'pino';

export const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', // apenas em dev options: { colorize: true }, }, });

Em produção, remova o pino-pretty e use saída pura JSON.

**Dica:** Inclua um requestId em cada log para rastrear uma requisição inteira. Use middleware que gera um UUID por requisição.

**Erro comum:** Logar dados sensíveis (senhas, tokens) em objetos. Sempre filtre campos antes de logar.

## Passo 5: Use injeção de dependência (ou pelo menos desacople)

Seu controller não precisa saber qual banco de dados você usa. Crie interfaces para repositórios e serviços, e injete as implementações.

// modules/user/user.repository.ts export interface IUserRepository { findById(id: string): Promise; }

// Implementação com Prisma export class UserPrismaRepository implements IUserRepository { async findById(id: string) { return prisma.user.findUnique({ where: { id } }); } }

**Dica:** Isso facilita testes unitários, você pode mockar o repositório sem precisar de banco de dados.

**Erro comum:** Criar dependências circulares. Se o serviço A depende do serviço B, e B depende de A, você tem um problema de design. Reveja os módulos.

## Passo 6: Padronize respostas da API

Não devolva objetos soltos. Crie um padrão de resposta:

{ "success": true, "data": { ... }, "meta": { "page": 1, "total": 100 } }

Para erros:

{ "success": false, "error": "Mensagem clara", "code": "VALIDATION_ERROR" }

**Dica:** Use um helper sendSuccess e sendError no controller para garantir consistência.

**Erro comum:** Retornar arrays soltos em endpoints de lista. Sempre envolva em um objeto com metadados.

## Checklist rápido

- [ ] Pastas organizadas por domínio, não por tipo
- [ ] Variáveis de ambiente validadas com schema
- [ ] Middleware de erro centralizado com distinção operacional vs programação
- [ ] Logger estruturado com níveis e requestId
- [ ] Interfaces desacopladas (injeção de dependência ou similar)
- [ ] Padrão de resposta da API unificado

## FAQ

### Qual a estrutura de pastas mais comum para Node.js em produção?

Não existe uma única, mas a mais difundida é a organização por módulos (domínios) com uma pasta shared/ para código reutilizável. Evite a estrutura plana de controllers/, models/, routes/.

### Devo usar TypeScript ou JavaScript puro?

TypeScript é fortemente recomendado para produção. A tipagem reduz erros de runtime e facilita refatorações. Se o time não domina TS, JavaScript com JSDoc é um meio-termo.

### Como lidar com variáveis de ambiente em produção?

Use um serviço de gerenciamento de segredos (AWS Secrets Manager, Vault) ou variáveis de ambiente do provedor de nuvem. Nunca suba .env com valores reais no repositório.

### O que fazer com erros de banco de dados?

Trate como erros operacionais, mas nunca exponha detalhes internos (tipo de banco, schema). Retorne um erro 500 genérico e logue o stack trace completo.

### Preciso de um framework como Express?

Não, mas a maioria dos projetos usa. Se você prefere minimalismo, fastify ou hono são alternativas mais performáticas e com tipagem nativa.

### Como testar a estrutura antes de subir para produção?

Escreva testes de integração que inicializam a aplicação e chamam endpoints reais. Se o setup for complexo, considere usar containers Docker para simular o ambiente de produção.

---

Fonte (canonical): https://blogsemjuizo.com.br/novidades/estruturar-nodejs-producao-guia-pratico-para-projetos-robustos/
