# Como Criar um Worker com Bull/BullMQ para Processar Consultas de CPF em Fila no Node.js

> Aprenda a criar workers com BullMQ para processar consultas de CPF em fila no Node.js, com retry automático, prioridades e monitoramento.

**Publicado:** 02/08/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/worker-bullmq-processar-consultas-cpf-fila-nodejs

---


Para processar consultas de CPF em escala com Node.js sem bloquear o servidor, crie um worker BullMQ que consome uma fila Redis: o endpoint responde imediatamente com um `jobId`, enquanto o worker consulta a API da CPFHub.io em background com retry automático, controle de concorrência e notificação por callback. Essa arquitetura suporta desde dezenas até milhares de CPFs por hora sem gargalos.

## Introdução

Processar consultas de CPF de forma síncrona durante o request/response pode ser aceitável para operações individuais, mas se torna um gargalo quando o volume cresce. Filas de processamento permitem desacoplar a requisição do processamento, garantindo que o sistema responda rapidamente enquanto os CPFs são validados em segundo plano. O BullMQ, baseado em Redis, é a solução mais robusta para filas em Node.js.

## Arquitetura do sistema com filas

A arquitetura separa produtores (que enfileiram CPFs) de consumidores (workers que processam).

| Componente | Responsabilidade | Tecnologia |
|---|---|---|
| Produtor | Recebe requisições e enfileira CPFs | Express + BullMQ |
| Fila | Armazena jobs pendentes com prioridade | Redis + BullMQ |
| Worker | Consome jobs e consulta a API de CPF | BullMQ Worker |
| Dashboard | Monitora status das filas | Bull Board |

**Desacoplamento** -- a API responde imediatamente ao cliente enquanto o processamento ocorre em background.

**Resiliência** -- se um worker falhar, o job é automaticamente reenfileirado para retry.

**Escalabilidade** -- múltiplos workers podem processar a mesma fila em paralelo.

---

## Configurando a fila e o produtor

Primeiro, configure a fila BullMQ e o endpoint que enfileira CPFs para processamento.

```javascript
import { Queue } from "bullmq";
import express from "express";

const conexaoRedis = {
 host: process.env.REDIS_HOST || "127.0.0.1",
 port: parseInt(process.env.REDIS_PORT) || 6379
};

const filaCPF = new Queue("consulta-cpf", { connection: conexaoRedis });

const app = express();
app.use(express.json());

// Endpoint para enfileirar um único CPF
app.post("/api/consultar-cpf", async (req, res) => {
 const { cpf, callback_url } = req.body;

 const job = await filaCPF.add(
 "consultar",
 { cpf, callback_url },
 {
 attempts: 3,
 backoff: { type: "exponential", delay: 2000 },
 removeOnComplete: { age: 3600 },
 removeOnFail: { age: 86400 }
 }
 );

 res.status(202).json({
 mensagem: "Consulta enfileirada com sucesso",
 jobId: job.id,
 status: "pendente"
 });
});

// Endpoint para enfileirar lote de CPFs
app.post("/api/consultar-cpf/lote", async (req, res) => {
 const { cpfs, prioridade } = req.body;

 const jobs = await filaCPF.addBulk(
 cpfs.map((cpf, index) => ({
 name: "consultar",
 data: { cpf, loteId: Date.now().toString() },
 opts: {
 priority: prioridade || 5,
 attempts: 3,
 backoff: { type: "exponential", delay: 2000 },
 delay: index * 100 // Espaçar requisições
 }
 }))
 );

 res.status(202).json({
 mensagem: `${jobs.length} CPFs enfileirados`,
 jobIds: jobs.map((j) => j.id)
 });
});

app.listen(3000, () => console.log("Servidor rodando na porta 3000"));
```

---

## Implementando o worker

O worker consome jobs da fila e processa cada consulta de CPF.

```javascript
import { Worker } from "bullmq";

const workerCPF = new Worker(
 "consulta-cpf",
 async (job) => {
 const { cpf, callback_url } = job.data;

 console.log(
 `[Job ${job.id}] Processando CPF: ${cpf} ` +
 `(tentativa ${job.attemptsMade + 1})`
 );

 // Consultar a API de CPF
 const response = await fetch(`https://api.cpfhub.io/cpf/${cpf}`, {
 headers: { "x-api-key": process.env.CPFHUB_API_KEY }
 });

 if (!response.ok) {
 throw new Error(`API retornou status ${response.status}`);
 }

 const resultado = await response.json();

 // Atualizar progresso
 await job.updateProgress(100);

 // Notificar via callback se configurado
 if (callback_url && resultado.success) {
 await fetch(callback_url, {
 method: "POST",
 headers: { "Content-Type": "application/json" },
 body: JSON.stringify({
 cpf,
 dados: resultado.data,
 jobId: job.id
 })
 });
 }

 return {
 cpf,
 sucesso: resultado.success,
 dados: resultado.data || null,
 processadoEm: new Date().toISOString()
 };
 },
 {
 connection: {
 host: process.env.REDIS_HOST || "127.0.0.1",
 port: parseInt(process.env.REDIS_PORT) || 6379
 },
 concurrency: 5,
 limiter: {
 max: 20,
 duration: 1000 // Máximo 20 jobs por segundo
 }
 }
);

workerCPF.on("completed", (job, resultado) => {
 console.log(`[Job ${job.id}] Concluído: ${resultado.cpf}`);
});

workerCPF.on("failed", (job, erro) => {
 console.error(`[Job ${job.id}] Falhou: ${erro.message}`);
});

workerCPF.on("error", (erro) => {
 console.error("Erro no worker:", erro);
});
```

---

## Monitoramento com Bull Board

O Bull Board fornece uma interface visual para monitorar o estado das filas.

```javascript
import { createBullBoard } from "@bull-board/api";
import { BullMQAdapter } from "@bull-board/api/bullMQAdapter.js";
import { ExpressAdapter } from "@bull-board/express";

const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath("/admin/filas");

createBullBoard({
 queues: [new BullMQAdapter(filaCPF)],
 serverAdapter
});

app.use("/admin/filas", serverAdapter.getRouter());
```

| Métrica da fila | Descrição | Meta |
|---|---|---|
| Jobs aguardando | Quantidade de CPFs na fila | < 100 |
| Jobs ativos | Sendo processados agora | Igual à concorrência |
| Jobs concluídos | Processados com sucesso | > 95% do total |
| Jobs falhados | Falharam após todas as tentativas | < 2% do total |
| Latência da fila | Tempo entre enfileirar e processar | < 5 segundos |

---

## Consulta de status do job

Para permitir que o cliente consulte o status de um job enfileirado, implemente um endpoint de consulta.

```javascript
app.get("/api/consultar-cpf/status/:jobId", async (req, res) => {
 const job = await filaCPF.getJob(req.params.jobId);

 if (!job) {
 return res.status(404).json({ erro: "Job não encontrado" });
 }

 const estado = await job.getState();
 const progresso = job.progress;

 res.json({
 jobId: job.id,
 estado,
 progresso,
 dados: job.data,
 resultado: job.returnvalue || null,
 tentativas: job.attemptsMade,
 criadoEm: new Date(job.timestamp).toISOString(),
 processadoEm: job.processedOn
 ? new Date(job.processedOn).toISOString()
 : null,
 concluidoEm: job.finishedOn
 ? new Date(job.finishedOn).toISOString()
 : null
 });
});
```

---

## Perguntas frequentes

### Por que usar BullMQ em vez de processar consultas de CPF de forma síncrona?
Consultas síncronas travam o servidor enquanto aguardam resposta da API externa — se houver pico de requisições ou instabilidade na rede, o tempo de resposta da sua aplicação dispara. Com BullMQ, o endpoint retorna imediatamente um `jobId` e o processamento real acontece no worker em background, com retry automático caso a consulta falhe.

### Quantos workers posso rodar em paralelo?
Não há limite fixo: cada instância do worker aceita um parâmetro `concurrency` e você pode subir múltiplos processos ou contêineres apontando para a mesma fila Redis. Para a [**CPFHub.io**](https://www.cpfhub.io/), observe o rate limit do seu plano — o plano Pro permite 1.000 consultas mensais, com excedente cobrado a R$0,15 por consulta sem bloqueio.

### Como tratar CPFs que falham mesmo após todas as tentativas de retry?
Configure `removeOnFail: false` e implemente um listener no evento `failed` do worker para mover esses jobs para uma dead letter queue ou registrá-los em banco de dados. A [ANPD](https://www.gov.br/anpd) recomenda que dados de identificação tratados por obrigação legal sejam rastreáveis — manter logs de falha faz parte dessa rastreabilidade.

### O BullMQ garante que cada CPF seja processado exatamente uma vez?
O BullMQ usa locks no Redis para garantir que um job seja processado por apenas um worker por vez. Em caso de crash do worker durante o processamento, o job retorna à fila após o tempo de lock expirar. Para evitar duplicatas no resultado final, use um identificador único (ex: CPF + timestamp do lote) ao salvar os resultados.

### Leia também

- [Diferença entre validação de CPF e consulta de CPF: quando usar cada uma](https://cpfhub.io/blog/diferenca-entre-validacao-de-cpf-e-consulta-de-cpf-quando-usar-cada-uma)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)
- [Onboarding digital em fintechs: como validar CPF em menos de 30 segundos](https://cpfhub.io/blog/onboarding-digital-em-fintechs-como-validar-cpf-em-menos-de-30-segundos)
- [KYC no Brasil: quais setores são obrigados a validar CPF por lei](https://cpfhub.io/blog/kyc-no-brasil-quais-setores-sao-obrigados-a-validar-cpf-por-lei)

---

## Conclusão

Utilizar BullMQ para processar consultas de CPF em fila é uma abordagem robusta que desacopla o tempo de resposta da API do tempo de processamento real. Com retry automático, controle de concorrência, rate limiting e monitoramento visual, o sistema se torna resiliente e escalável. Essa arquitetura é especialmente valiosa para operações em lote, integrações com sistemas legados e cenários onde a disponibilidade da API não pode ser garantida.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar consultas de CPF em fila com Node.js hoje mesmo.

