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.

Redação CPFHub.io
Redação CPFHub.io
··7 min de leitura
Como Criar um Worker com Bull/BullMQ para Processar Consultas de CPF em Fila no Node.js

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).

ComponenteResponsabilidadeTecnologia
ProdutorRecebe requisições e enfileira CPFsExpress + BullMQ
FilaArmazena jobs pendentes com prioridadeRedis + BullMQ
WorkerConsome jobs e consulta a API de CPFBullMQ Worker
DashboardMonitora status das filasBull 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.

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.

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.

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 filaDescriçãoMeta
Jobs aguardandoQuantidade de CPFs na fila< 100
Jobs ativosSendo processados agoraIgual à concorrência
Jobs concluídosProcessados com sucesso> 95% do total
Jobs falhadosFalharam após todas as tentativas< 2% do total
Latência da filaTempo 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.

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, 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 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.


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 — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a processar consultas de CPF em fila com Node.js hoje mesmo.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp