Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Dependency Injection no Node.js: código testável

Atualizado em: 13 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

Dependency Injection no Node.js é a prática de fornecer dependências a uma função, classe ou módulo em vez de criá-las internamente. Repositórios, clientes HTTP, relógios, loggers e geradores de IDs passam a ser recebidos por parâmetro.

Essa técnica reduz acoplamento, facilita testes e torna o ciclo de vida de recursos explícito. Ela não exige um container complexo: fábricas e objetos JavaScript resolvem a maioria dos casos.

Neste guia, você aprenderá injeção por função, construtor, fábricas, composição, escopos, testes e erros comuns.

Dependência escondida

import { db } from './database.js';

export async function criarUsuario(input) {
  return db.users.insert(input);
}

A função depende de um singleton importado. Testes precisam manipular o módulo global e o recurso é criado fora do controle da aplicação.

Dependência explícita

export function criarUsuarioService({ repository, clock, idGenerator }) {
  return {
    async criar(input) {
      const user = {
        id: idGenerator.next(),
        email: input.email,
        createdAt: clock.now()
      };
      await repository.salvar(user);
      return user;
    }
  };
}

A função de fábrica recebe tudo o que precisa e retorna o serviço configurado.

Composition root

Crie dependências em um ponto de entrada:

const pool = criarPool(config.databaseUrl);
const repository = criarUsuarioRepository(pool);
const service = criarUsuarioService({
  repository,
  clock: systemClock,
  idGenerator: uuidGenerator
});
const app = criarApp({ userService: service, logger });

Esse local é chamado de composition root. Ele conecta infraestrutura e regras de negócio.

Injeção por construtor

class UsuarioService {
  constructor({ repository, logger }) {
    this.repository = repository;
    this.logger = logger;
  }
}

É útil para classes com várias operações e ciclo de vida estável.

Injeção por função

export async function executarCaso(input, { repository, logger }) {
  // regra
}

Funções simples evitam classes desnecessárias e deixam dependências visíveis na assinatura.

Interfaces implícitas

JavaScript não exige interface formal. Defina contratos pequenos:

// repository esperado
{
  salvar(entity),
  buscar(id)
}

Validação em startup pode detectar dependência incompleta.

Testes

const repository = {
  saved: [],
  async salvar(entity) {
    this.saved.push(entity);
  }
};

const service = criarUsuarioService({
  repository,
  clock: { now: () => new Date('2026-01-01T00:00:00Z') },
  idGenerator: { next: () => 'u-1' }
});

Veja Mocking no Node.js para spies e fakes.

Escopos

  • singleton: uma instância por processo, como pool;
  • request: contexto por requisição;
  • transient: nova instância a cada resolução.

Não transforme tudo em singleton. Estado por requisição deve permanecer isolado.

AsyncLocalStorage

Contexto de request, como correlation ID, pode ser propagado por AsyncLocalStorage no Node.js. Ele complementa DI, mas não deve esconder dependências de negócio.

Container manual

export function criarContainer(config) {
  const pool = criarPool(config.databaseUrl);
  const logger = criarLogger(config.logLevel);
  const userRepository = criarUsuarioRepository(pool);
  const userService = criarUsuarioService({ userRepository, logger });

  return { pool, logger, userService };
}

Um container manual é fácil de entender e depurar.

Containers automáticos

Bibliotecas de DI podem resolver grafos maiores, mas introduzem convenções, metadados e erros em runtime. Use apenas quando o ganho superar a complexidade.

Dependências opcionais

Evite parâmetro opcional que muda silenciosamente comportamento. Forneça uma implementação nula explícita:

const noopMetrics = { increment() {} };

Ciclo de vida

O composition root deve iniciar e encerrar recursos:

await pool.connect();
const server = app.listen(port);

async function shutdown() {
  server.close();
  await pool.end();
}

Veja Graceful Shutdown no Node.js.

Ciclos de dependência

Se serviço A depende de B e B depende de A, extraia uma responsabilidade ou use eventos. Containers que resolvem o ciclo não corrigem o design.

Erros comuns

  • usar service locator global;
  • injetar objeto gigante;
  • criar dependência dentro do serviço;
  • usar singleton com estado de request;
  • ocultar ciclo;
  • mockar tudo;
  • adotar container complexo cedo demais.

Checklist

  • dependências explícitas;
  • composition root único;
  • contratos pequenos;
  • ciclo de vida controlado;
  • escopos documentados;
  • fakes simples;
  • sem service locator;
  • shutdown testado.

Conclusão

Dependency Injection no Node.js melhora o design ao tornar conexões entre componentes visíveis. Fábricas e parâmetros são suficientes para grande parte das aplicações.

Comece simples, centralize a composição e injete somente dependências reais. O resultado é código mais testável, recursos controlados e menos acoplamento ao runtime.

Consulte a documentação de módulos do Node.js e o artigo de Martin Fowler sobre injeção de dependência.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita