Skip to content

Introduction

Installation

Add BullMQ to your project via Cargo:

bash
cargo add bullmq-official --rename bullmq

Or add it to your Cargo.toml:

toml
[dependencies]
bullmq = { version = "1.2", package = "bullmq-official" }

The crate is published as bullmq-official (the bullmq, bullmq-rust and bullmq-rs names on crates.io are already taken by unrelated third-party crates), but it is imported in code as bullmq — e.g. use bullmq::{Queue, Worker};.

BullMQ for Rust requires:

  • Rust 1.85+
  • Tokio runtime
  • Redis 6.2+

Get Started

BullMQ uses Tokio for async processing. All operations are non-blocking and designed for high-throughput concurrent workloads.

Adding Jobs to a Queue

rust
use bullmq::{Queue, QueueOptions};

#[tokio::main]
async fn main() -> bullmq::Result<()> {
    let queue = Queue::new("my-queue", QueueOptions::default()).await?;

    // Add a job with JSON data
    queue.add("my-job", serde_json::json!({
        "foo": "bar"
    }), None).await?;

    Ok(())
}

Processing Jobs with a Worker

rust
use bullmq::{Worker, WorkerOptions, Job};
use bullmq::worker::{ProcessorFn, CancellationToken};
use std::sync::Arc;

#[tokio::main]
async fn main() -> bullmq::Result<()> {
    let processor: ProcessorFn = Arc::new(|job: Job, _token: CancellationToken| {
        Box::pin(async move {
            println!("Processing job: {} - {}", job.id(), job.name());
            // Access job data
            let data = job.data();
            println!("Data: {}", data);
            // Return a result value
            Ok(serde_json::json!({"processed": true}))
        })
    });

    let worker = Worker::new("my-queue", processor, WorkerOptions::default()).await?;

    // Worker processes jobs automatically in the background.
    // Wait for a signal or condition to shut down.
    tokio::signal::ctrl_c().await.unwrap();

    // Graceful shutdown with 5-second timeout
    worker.close(5000).await?;
    Ok(())
}

Listening to Worker Events

rust
use bullmq::{Worker, WorkerOptions, Job};
use bullmq::worker::{ProcessorFn, CancellationToken};
use std::sync::Arc;

#[tokio::main]
async fn main() -> bullmq::Result<()> {
    let processor: ProcessorFn = Arc::new(|job: Job, _token: CancellationToken| {
        Box::pin(async move {
            Ok(serde_json::json!(null))
        })
    });

    let worker = Worker::new("my-queue", processor, WorkerOptions::default()).await?;

    // Consume events from the worker
    while let Some(event) = worker.next_event().await {
        match event {
            bullmq::worker::WorkerEvent::Completed { job_id, result } => {
                println!("Job {} completed with: {}", job_id, result);
            }
            bullmq::worker::WorkerEvent::Failed { job_id, error } => {
                println!("Job {} failed: {}", job_id, error);
            }
            bullmq::worker::WorkerEvent::Active { job_id } => {
                println!("Job {} started processing", job_id);
            }
            _ => {}
        }
    }

    Ok(())
}

Concurrency

Configure how many jobs are processed simultaneously:

rust
let worker = Worker::new("my-queue", processor, WorkerOptions {
    concurrency: 10,
    ..Default::default()
}).await?;

The concurrency can be changed dynamically at runtime:

rust
worker.set_concurrency(20);

Progress Tracking

Report progress from inside the processor:

rust
let processor: ProcessorFn = Arc::new(|mut job: Job, _token: CancellationToken| {
    Box::pin(async move {
        for i in 0..100 {
            // Do work...
            job.update_progress(bullmq::JobProgress::Number(i as f64)).await?;
        }
        Ok(serde_json::json!(null))
    })
});

Job Retries with Backoff

rust
use bullmq::{Queue, QueueOptions, JobOptions};
use bullmq::types::BackoffStrategy;

let queue = Queue::new("my-queue", QueueOptions::default()).await?;

queue.add("flaky-job", serde_json::json!({}), Some(JobOptions {
    attempts: Some(5),
    backoff: Some(BackoffStrategy::Exponential(1000)), // 1s, 2s, 4s, 8s, 16s
    ..Default::default()
})).await?;

Connection Configuration

rust
use bullmq::{QueueOptions, WorkerOptions};
use bullmq::options::RedisConnectionOptions;

let conn = RedisConnectionOptions {
    url: "redis://user:password@redis.example.com:6380".to_string(),
    ..Default::default()
};

let queue = Queue::new("my-queue", QueueOptions {
    connection: conn.clone(),
    ..Default::default()
}).await?;

let worker = Worker::new("my-queue", processor, WorkerOptions {
    connection: conn,
    ..Default::default()
}).await?;

Instead of a URL you can use typed connection fields. When host is set, the URL is built from these fields (use tls: true for a rediss:// TLS connection):

rust
use bullmq::options::RedisConnectionOptions;

let conn = RedisConnectionOptions {
    host: Some("redis.example.com".to_string()),
    port: Some(6380),
    username: Some("user".to_string()),
    password: Some("password".to_string()),
    db: Some(0),
    tls: true,
    ..Default::default()
};

Custom TLS certificates

To connect over TLS with a custom root CA (for example a self-signed certificate) or a client certificate and key for mutual TLS (mTLS), provide a tls_certs value. All certificates and keys must be in PEM format. Setting tls_certs implies a TLS (rediss://) connection, so tls: true is not required:

rust
use bullmq::options::{RedisConnectionOptions, TlsCerts};

let conn = RedisConnectionOptions {
    host: Some("redis.example.com".to_string()),
    port: Some(6380),
    tls_certs: Some(TlsCerts {
        // Custom CA certificate (omit to use the default WebPKI root store).
        root_cert: Some(std::fs::read("ca.pem")?),
        // Client certificate and key for mutual TLS (mTLS).
        client_cert: Some(std::fs::read("client-cert.pem")?),
        client_key: Some(std::fs::read("client-key.pem")?),
    }),
    ..Default::default()
};

Key Differences from Node.js

AspectNode.jsRust
RuntimeEvent loop (single-threaded)Tokio (multi-threaded async)
Processorasync function or sandboxed fileArc<dyn Fn(Job, CancellationToken) -> Pin<Box<...>>>
EventsEventEmitter patternmpsc::UnboundedReceiver<WorkerEvent>
Error handlingExceptionsResult<T, Error> types
CancellationAbortSignalCancellationToken
ConcurrencyCooperative (single core)True parallelism across all CPU cores

Compatibility

The Rust implementation uses the same Lua scripts and Redis data structures as the Node.js and Python versions. This means:

  • Jobs added by Node.js workers can be processed by Rust workers (and vice versa)
  • Queue state is fully shared across all language implementations
  • You can mix languages in a single deployment

Released under the MIT License.