Usar NATS no Node.js permite criar comunicação rápida entre serviços com publish/subscribe, request/reply, queue groups e persistência por JetStream. O protocolo é baseado em subjects, que funcionam como endereços hierárquicos para mensagens.
Core NATS prioriza baixa latência e entrega at-most-once, enquanto JetStream adiciona retenção, consumidores duráveis, ACK, replay e políticas de entrega. A escolha depende da importância da mensagem e da necessidade de recuperação.
Neste guia, você aprenderá conexão, subjects, publish/subscribe, request/reply, queue groups, JetStream, consumers, ACK, retries, idempotência, segurança, observabilidade e testes.
O que é NATS?
NATS é uma plataforma de mensageria distribuída. A documentação oficial para desenvolvimento com NATS apresenta conexão, envio, recebimento, serviços e JetStream. O cliente JavaScript oficial está no projeto nats.js.
Para comparar com brokers persistentes, consulte RabbitMQ com Node.js e Kafka com Node.js.
Instalação
npm install natsConectando
import { connect, StringCodec } from 'nats';
const nc = await connect({
servers: process.env.NATS_SERVERS?.split(','),
name: 'orders-api',
maxReconnectAttempts: -1,
reconnectTimeWait: 1000
});
const codec = StringCodec();Em produção, use vários servidores ou uma URL descoberta pelo ambiente.
Eventos de status
(async () => {
for await (const status of nc.status()) {
logger.info({
type: status.type,
data: status.data
}, 'Status da conexão NATS');
}
})();Monitore desconexão, reconexão e atualização de servidores.
Publish básico
nc.publish(
'orders.created',
codec.encode(JSON.stringify({
eventId: crypto.randomUUID(),
orderId,
version: 1
}))
);Core NATS envia a mensagem aos assinantes ativos. Se não houver subscriber e não existir JetStream, a mensagem não fica armazenada.
Flush
await nc.flush();publish() normalmente coloca dados no buffer local. flush() confirma que o servidor processou tudo enviado anteriormente, mas não garante que consumidores concluíram.
Subscribe
const subscription = nc.subscribe('orders.created');
for await (const message of subscription) {
const event = JSON.parse(codec.decode(message.data));
await handleOrderCreated(event);
}O loop assíncrono termina quando a subscription é fechada ou a conexão é drenada.
Subjects
Subjects são hierárquicos:
orders.created
orders.approved
payments.authorized
inventory.reservedUse nomes estáveis, orientados ao domínio e sem dados pessoais.
Wildcards
*corresponde a um token.>corresponde ao restante da hierarquia.
nc.subscribe('orders.*');
nc.subscribe('events.>');Evite subscriptions muito amplas sem limites de processamento.
Queue groups
const sub = nc.subscribe(
'orders.created',
{ queue: 'billing-workers' }
);Dentro do mesmo queue group, apenas um consumidor recebe cada mensagem. Grupos diferentes recebem cópias independentes.
Escalando consumidores
Várias instâncias com o mesmo queue group distribuem carga. O balanceamento não substitui idempotência: mensagens podem ser reenviadas em arquiteturas persistentes ou por retries do produtor.
Request/reply
const response = await nc.request(
'inventory.check',
codec.encode(JSON.stringify({ items })),
{ timeout: 1000 }
);
const result = JSON.parse(codec.decode(response.data));NATS cria uma inbox temporária e aguarda uma resposta.
Respondendo
for await (const message of nc.subscribe('inventory.check')) {
const input = JSON.parse(codec.decode(message.data));
const output = await inventory.check(input.items);
message.respond(
codec.encode(JSON.stringify(output))
);
}Se não houver reply subject, respond() retorna false.
Timeouts
Request/reply sempre deve ter timeout. Uma resposta ausente não pode manter a requisição HTTP indefinidamente.
No responders
Quando nenhum serviço responde, o cliente pode receber erro específico. Diferencie indisponibilidade de timeout e resposta de negócio negativa.
Headers
import { headers } from 'nats';
const h = headers();
h.set('content-type', 'application/json');
h.set('traceparent', traceparent);
h.set('message-version', '1');
nc.publish('orders.created', data, { headers: h });Não coloque segredos nos headers.
Payload binário
NATS transporta bytes. JSON é simples, mas Protocol Buffers, MessagePack ou Avro podem reduzir tamanho e fortalecer contratos. Versione qualquer formato.
Core NATS versus JetStream
- Core NATS: at-most-once, baixa latência, subscribers ativos.
- JetStream: mensagens armazenadas, consumers, ACK, replay e retenção.
Use Core para sinais efêmeros e JetStream quando a mensagem não pode ser perdida durante desconexão.
Criando contexto JetStream
const js = nc.jetstream();
const jsm = await nc.jetstreamManager();Criando stream
await jsm.streams.add({
name: 'ORDERS',
subjects: ['orders.>'],
max_age: 7 * 24 * 60 * 60 * 1_000_000_000,
storage: 'file',
retention: 'limits'
});Parâmetros exatos e enums dependem da versão do cliente. Verifique a API atual antes de copiar para produção.
Publicando no JetStream
const ack = await js.publish(
'orders.created',
codec.encode(JSON.stringify(event)),
{
msgID: event.eventId
}
);O servidor retorna confirmação com stream e sequência. msgID pode ajudar a deduplicar publicações dentro da janela configurada.
Consumer durável
Consumers duráveis preservam estado entre reinícios. Configure:
- durable name;
- filter subject;
- ack policy;
- ack wait;
- max deliveries;
- delivery policy;
- backoff.
Pull consumer
Pull consumers permitem controlar lote e backpressure:
const consumer = await js.consumers.get(
'ORDERS',
'billing'
);
const messages = await consumer.consume({
max_messages: 20,
expires: 5000
});
for await (const message of messages) {
try {
const event = JSON.parse(codec.decode(message.data));
await handle(event);
message.ack();
} catch (error) {
message.nak(1000);
}
}Confirme a sintaxe da versão instalada; a API moderna do nats.js evolui junto com JetStream.
ACK
ack(): processamento concluído.nak(): falhou e deve ser entregue novamente.term(): não deve ser reenviado.working(): estende prazo durante tarefa longa.
Não faça ACK antes de concluir o efeito.
Ack wait
O prazo precisa ser maior que a duração normal do handler. Caso contrário, a mesma mensagem pode ser entregue a outro consumidor enquanto o primeiro ainda trabalha.
Max deliveries
Defina um limite para mensagens permanentemente inválidas. Depois envie para um subject de dead-letter ou gere um evento de falha.
Dead-letter
Uma estratégia:
await js.publish(
'orders.dead-letter',
message.data,
{
headers: deadLetterHeaders
}
);
message.term();Inclua ID original, tipo de erro, tentativas e horário, sem expor segredo.
Idempotência
JetStream oferece entrega at-least-once em cenários comuns. Consumidores devem deduplicar por eventId.
INSERT INTO processed_events (event_id)
VALUES ($1)
ON CONFLICT DO NOTHING;Faça a deduplicação na mesma transação do efeito. Consulte Idempotência em APIs Node.js.
Outbox
Não atualize banco e publique no NATS como duas operações sem coordenação. Use outbox e um relay.
Consulte Domain Events no Node.js.
Ordenação
NATS preserva ordem por publisher e subject em condições normais, mas processamento paralelo pode concluir fora de ordem. Inclua aggregateVersion e detecte lacunas quando necessário.
Particionamento por subject
orders.customer.42.createdColocar IDs no subject pode aumentar cardinalidade operacional e complexidade de permissões. Use somente quando a estratégia de roteamento justificar.
Serviços NATS
A infraestrutura de serviços oferece endpoints, grupos, metadata e observabilidade para request/reply. É útil para APIs internas leves, mas contratos e timeouts continuam necessários.
Drain
await nc.drain();Drain para de aceitar novas mensagens, permite concluir mensagens em trânsito e fecha a conexão.
Shutdown de consumidor
- pare novas leituras;
- cancele fetch ou consume;
- aguarde handlers;
- faça ACK apenas de concluídos;
- execute drain;
- feche recursos.
Consulte Graceful Shutdown no Node.js.
Segurança
NATS suporta:
- usuário e senha;
- tokens;
- NKEYs;
- JWT e accounts;
- TLS e mTLS;
- permissões por subject.
Prefira credenciais temporárias ou arquivos protegidos e privilégio mínimo.
mTLS
Para autenticação mútua entre serviços, consulte mTLS no Node.js.
Subject permissions
Um serviço de billing pode publicar apenas respostas e assinar subjects necessários. Não use permissão global > sem necessidade.
Observabilidade
Monitore:
- conexões;
- reconexões;
- mensagens e bytes;
- latência request/reply;
- consumer lag;
- redeliveries;
- ACK pendentes;
- dead letters;
- armazenamento do stream.
Correlation e tracing
Propague traceparent em headers e crie spans de publish e consume. Evite criar cardinalidade com eventId em métricas.
Testes
Use um servidor NATS real em container e teste:
- publish/subscribe;
- queue groups;
- request/reply e timeout;
- JetStream publish ACK;
- consumer durável;
- redelivery;
- dead-letter;
- idempotência;
- drain.
Teste de desconexão
Interrompa o servidor, publique durante reconexão conforme a política do cliente e confirme comportamento esperado. Verifique limites de buffer.
Erros comuns
- Core para evento crítico: mensagem é perdida sem subscriber.
- Sem timeout: request/reply fica pendurado.
- ACK antecipado: efeito pode não ocorrer.
- Sem idempotência: redelivery duplica.
- Subject desorganizado: permissões ficam difíceis.
- Payload sem versão: consumidores quebram.
- Ack wait curto: processamento concorrente duplica.
- Fechar sem drain: mensagens em trânsito são perdidas.
Boas práticas
- Escolha Core ou JetStream conscientemente.
- Use subjects estáveis.
- Defina timeout.
- Controle queue groups.
- Versione payloads.
- Faça ACK após sucesso.
- Implemente idempotência.
- Use outbox.
- Restrinja permissões.
- Teste reconexão e redelivery.
Conclusão
Usar NATS no Node.js fornece publish/subscribe e request/reply com baixa latência. Queue groups facilitam escala horizontal, enquanto JetStream adiciona retenção e consumidores duráveis.
A confiabilidade depende de escolher o modo correto, usar timeouts, ACK, idempotência e outbox. Com segurança por subject, observabilidade e drain no shutdown, NATS oferece uma base simples para comunicação entre serviços.




