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

RabbitMQ com Node.js

Atualizado em: 19 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

RabbitMQ com Node.js permite desacoplar partes de uma aplicação usando filas e exchanges. Em vez de uma requisição HTTP esperar que todo o processamento termine, o produtor publica uma mensagem e um ou mais consumidores executam o trabalho de forma independente. Essa arquitetura ajuda em envio de e-mails, geração de relatórios, processamento de imagens, integrações e tarefas que precisam sobreviver a falhas temporárias.

Uma fila não garante confiabilidade sozinha. É necessário configurar durabilidade, confirmação de publicação, acknowledgement manual, retries controlados, dead-letter, idempotência e observabilidade. Neste guia, você verá como construir esse fluxo com Node.js. Para revisar a base da plataforma, leia o que é Node.js e como criar uma API com Node.js.

Como o RabbitMQ organiza mensagens?

Produtores publicam mensagens em exchanges. A exchange encaminha cada mensagem para uma ou mais filas de acordo com bindings e routing keys. Consumidores recebem mensagens das filas. Esse modelo evita que o produtor precise conhecer cada consumidor e facilita adicionar novos fluxos sem alterar o código que cria o evento.

Os tipos mais comuns de exchange são direct, topic, fanout e headers. Uma exchange direct usa correspondência exata da routing key. Topic aceita padrões como orders.*. Fanout distribui para todas as filas vinculadas. Escolha o tipo a partir do contrato de roteamento, não apenas do exemplo mais simples.

Instalação

npm init -y
npm install amqplib
npm install --save-dev node:test

Configure a URL de conexão por variável de ambiente e use TLS quando o broker estiver fora de uma rede privada controlada. Não coloque credenciais no repositório.

Declarando exchange e fila

import amqp from 'amqplib';

const connection = await amqp.connect(process.env.AMQP_URL);
const channel = await connection.createConfirmChannel();

await channel.assertExchange('orders.events', 'topic', {
  durable: true
});

await channel.assertQueue('billing.order-created', {
  durable: true,
  deadLetterExchange: 'orders.dead'
});

await channel.bindQueue(
  'billing.order-created',
  'orders.events',
  'order.created'
);

Declarar recursos no início torna o serviço autocontido, mas várias equipes devem coordenar alterações incompatíveis. Em ambientes maiores, a topologia pode ser gerenciada por infraestrutura como código.

Publicando com confirmação

Mensagens persistentes e filas duráveis aumentam a chance de sobrevivência a reinícios, mas o produtor também precisa confirmar que o broker aceitou a publicação.

export async function publishOrderCreated(order) {
  const payload = Buffer.from(JSON.stringify({
    eventId: crypto.randomUUID(),
    eventType: 'order.created',
    occurredAt: new Date().toISOString(),
    data: {
      orderId: order.id,
      customerId: order.customerId,
      total: order.total
    }
  }));

  channel.publish(
    'orders.events',
    'order.created',
    payload,
    {
      persistent: true,
      contentType: 'application/json',
      messageId: crypto.randomUUID(),
      timestamp: Date.now()
    }
  );

  await channel.waitForConfirms();
}

Publisher confirms informam que o broker assumiu responsabilidade pela mensagem. Eles não significam que um consumidor processou o evento. Para operações importantes, registre o estado de publicação e considere o padrão transactional outbox para alinhar banco e mensageria.

Consumidor com ack manual

await channel.prefetch(10);

await channel.consume(
  'billing.order-created',
  async message => {
    if (!message) return;

    try {
      const event = JSON.parse(message.content.toString('utf8'));
      validateOrderCreated(event);
      await processBillingOnce(event);
      channel.ack(message);
    } catch (error) {
      if (isTemporary(error)) {
        channel.nack(message, false, true);
      } else {
        channel.nack(message, false, false);
      }
    }
  },
  { noAck: false }
);

O ack deve ser enviado somente depois que todos os efeitos necessários foram concluídos. Se o processo morrer antes do ack, o RabbitMQ pode entregar a mensagem novamente. Por isso, o consumidor precisa ser idempotente.

Prefetch e concorrência

prefetch limita quantas mensagens não confirmadas podem ficar com o consumidor. Um valor alto aumenta paralelismo, mas pode concentrar trabalho, memória e conexões em uma única instância. Meça duração, CPU, banco e APIs externas antes de escolher o valor.

Não abra um canal por mensagem. Mantenha conexões longas, use canais com responsabilidade clara e recrie-os quando a conexão cair. O reconnect precisa de backoff com jitter, como explicado em retry com backoff no Node.js.

Retries sem loop infinito

Reenfileirar imediatamente uma mensagem problemática pode criar um loop que consome toda a fila. Prefira filas de retry com TTL e dead-letter exchange. Cada tentativa incrementa um contador confiável; após o limite, a mensagem vai para uma dead-letter queue para análise.

  • classifique falhas temporárias e permanentes;
  • defina número máximo de tentativas;
  • use atrasos crescentes;
  • preserve eventId e correlationId;
  • não altere o payload silenciosamente;
  • crie procedimento para reprocessamento seguro.

Idempotência

Entregas duplicadas são esperadas em sistemas at-least-once. Guarde o eventId processado em uma tabela com restrição única ou faça a própria operação de negócio ser idempotente. O registro e o efeito devem ocorrer na mesma transação quando possível.

async function processBillingOnce(event) {
  await database.transaction(async tx => {
    const inserted = await tx.tryInsertProcessedEvent(event.eventId);
    if (!inserted) return;

    await tx.createInvoice({
      orderId: event.data.orderId,
      total: event.data.total
    });
  });
}

Contratos de mensagem

Defina versão, tipo, identificador, horário e payload. Valide mensagens em runtime; tipos TypeScript não protegem dados vindos da rede. Consulte Zod no TypeScript para criar schemas executáveis.

Evite incluir dados pessoais desnecessários. Mensagens podem permanecer no broker, em backups e em dead-letter queues por mais tempo que uma requisição HTTP.

Segurança

  • use usuários separados por serviço;
  • conceda permissões apenas às exchanges e filas necessárias;
  • ative TLS e valide certificados;
  • rotacione credenciais;
  • limite tamanho máximo das mensagens;
  • não use desserialização insegura;
  • proteja a interface administrativa;
  • monitore publicação e consumo anormais.

Observabilidade

Acompanhe profundidade da fila, taxa de publicação, taxa de entrega, mensagens não confirmadas, redeliveries, idade da mensagem mais antiga, tempo de processamento e conteúdo da dead-letter queue. Logs devem incluir eventId, messageId, routing key, tentativa e requestId original.

Não use eventId como label de métrica. Ele serve para logs e traces. Métricas precisam de dimensões limitadas para não aumentar a cardinalidade.

Graceful shutdown

Ao receber SIGTERM, pare de aceitar novas requisições, cancele o consumidor, aguarde tarefas em andamento, envie os acks e feche canal e conexão. Defina um prazo máximo para que o orquestrador não mate o processo no meio da confirmação.

process.on('SIGTERM', async () => {
  await channel.cancel(consumerTag);
  await waitForInFlightJobs({ timeoutMs: 20_000 });
  await channel.close();
  await connection.close();
  process.exit(0);
});

Testes

Teste validação de payload, duplicidade, falha temporária, falha permanente, retry, dead-letter e encerramento. Em integração, use um broker isolado e nomes de filas únicos por suíte. Confirme que uma mensagem não recebe ack quando a transação falha.

Os princípios de testes unitários com Jest ajudam a separar regras de negócio da infraestrutura.

Erros comuns

  • Usar auto-ack: a mensagem pode ser perdida antes do processamento;
  • Requeue infinito: uma mensagem inválida bloqueia capacidade continuamente;
  • Ignorar duplicidade: efeitos como cobrança podem acontecer duas vezes;
  • Publicar sem confirmação: o produtor pode assumir sucesso antes do broker;
  • Mensagens gigantes: memória e rede são consumidas de forma desnecessária;
  • Sem dead-letter: falhas permanentes desaparecem no fluxo operacional.

Checklist de produção

  • exchange e filas são duráveis;
  • publicações usam confirmação;
  • consumidores usam ack manual;
  • prefetch foi medido;
  • retries têm atraso e limite;
  • dead-letter queue é monitorada;
  • operações são idempotentes;
  • shutdown foi testado.

Referências oficiais

Conclusão

RabbitMQ com Node.js oferece uma base sólida para trabalho assíncrono, desde que a aplicação trate as garantias reais do sistema. Publisher confirms, ack manual, prefetch, retry com atraso, dead-letter e idempotência são partes do mesmo desenho.

Comece com um contrato simples e observável. Depois teste quedas do consumidor, indisponibilidade do broker e mensagens duplicadas. Uma fila confiável não é aquela que nunca falha, mas aquela que preserva o trabalho, torna falhas visíveis e permite recuperação segura.

Os 10 Melhores Cursos de Programação de 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