Skip to content
Get started
Get started
Quick Start: Your First Bun Job Queue in Minutes
View Markdown
guide · quickstart

Working queue in a minute.

From nothing to a processed job in about a minute, in Node.js, Bun, Deno, Python, PHP, Go, Rust or Elixir. Copy, run, see it work, then add retries, persistence and the rest.

Pick how you run bunqueue. Both ways are free and have the same queue features.

  1. Start the server. With Docker running, no Bun needed:

    docker run -d --name bunqueue \
    --restart unless-stopped \
    -p 127.0.0.1:6789:6789 \
    -p 127.0.0.1:6790:6790 \
    -v bunqueue-data:/app/data \
    egeominotti/bunqueue:alpine

    Or, with Bun installed, run it directly: bunx bunqueue start --host 127.0.0.1 --data-path ./bunqueue.db. Check that it is ready:

    curl --fail http://127.0.0.1:6790/health
  2. Add a job and process it. In another terminal, install the client for your language, save the file and run it.

    npm install bunqueue-client
    jobs.mjs
    import { Queue, Worker } from 'bunqueue-client';
    const options = {
    embedded: false,
    connection: { host: '127.0.0.1', port: 6789 },
    };
    const queue = new Queue('emails', options);
    const worker = new Worker(
    'emails',
    async (job) => {
    console.log('Processing:', job.data.to);
    return { sent: true };
    },
    options
    );
    worker.on('error', (error) => console.error(error));
    await queue.add('welcome', { to: 'hello@example.com' });
    node jobs.mjs
  3. See it work. The worker prints the job your app just added, then keeps waiting for more (press Ctrl+C to stop it):

    Processing: hello@example.com
You want toRead
Retry failed jobs and see why they failedErrors & retries, Dead letter queue
Run something every hour or every dayCron
Keep jobs when the process restartsTurn on persistence
Watch queues and jobs in a browserWeb dashboard
Let an AI agent manage the queueMCP server
Run jobs in a chain or a treeFlows, Workflow engine

Prefer to watch? The first bunqueue Academy episode walks through the same steps.

Video bunqueue Academy 01: Quickstart, 19:14. All episodes

The rest of this page builds on the same queue and worker: job options, the processor, events, persistence and a clean shutdown.

// Typed queue: job.data is type-checked
interface EmailJob {
to: string;
subject: string;
}
const emailQueue = new Queue<EmailJob>('emails', { embedded: true });
// Priority, delay, retries
await emailQueue.add(
'send-email',
{ to: 'a@test.com', subject: 'Hi' },
{
priority: 10, // Higher = processed first
delay: 5000, // Wait 5 seconds before processing
attempts: 3, // At most 3 attempts (2 retries) if the processor throws
backoff: 1000, // Wait 1 second between retries (grows on each attempt)
}
);
// Many jobs at once (one optimized batch)
await emailQueue.addBulk([
{ name: 'send-email', data: { to: 'a@test.com', subject: 'Hi' } },
{ name: 'send-email', data: { to: 'b@test.com', subject: 'Hi' } },
]);

All options are in the Queue guide.

const worker = new Worker<EmailJob>(
'emails',
async (job) => {
await job.updateProgress(50, 'Sending email...'); // Report progress
await sendEmail(job.data); // Do the work
await job.log('Email sent successfully'); // Attach a log line
return { sent: true, timestamp: Date.now() }; // Result, stored and queryable
},
{
embedded: true,
concurrency: 5, // Process 5 jobs in parallel
}
);
worker.on('completed', (job, result) => {
console.log(`Job ${job.id} completed:`, result);
});
worker.on('failed', (job, error) => {
console.error(`Job ${job.id} failed:`, error.message);
});
worker.on('progress', (job, progress) => {
console.log(`Job ${job.id} progress: ${progress}%`);
});

The full event list is in the Worker guide.

Without a data path, jobs live in memory and disappear on restart. Point bunqueue at a SQLite file to survive restarts:

// Option 1: dataPath option (recommended)
const queue = new Queue('tasks', { embedded: true, dataPath: './data/bunqueue.db' });
const worker = new Worker('tasks', processor, { embedded: true, dataPath: './data/bunqueue.db' });
// Option 2: environment variable
// BUNQUEUE_DATA_PATH=./data/bunqueue.db bun run app.ts

Every embedded Queue and Worker in the process shares one database. Naming the same dataPath again is fine; naming a different one throws instead of silently opening a second database.

In server mode persistence is configured once on the server, and no client in any language changes:

bunx bunqueue start --data-path ./data/bunq.db

dataPath is an embedded (Bun) option only. BUNQUEUE_DATA_PATH is the canonical variable; BQ_DATA_PATH, DATA_PATH and SQLITE_PATH are still read, in that order, as fallbacks.

import { shutdownManager } from 'bunqueue/client';
process.on('SIGINT', async () => {
await worker.close(); // Finish active jobs
shutdownManager(); // Flush pending writes, close SQLite
process.exit(0);
});

In embedded mode, queue.close() also writes the jobs still in the SQLite write buffer before it returns, so an awaited add() survives an immediate process.exit(). It leaves the process-wide timers running, so call shutdownManager() once every embedded client is closed: that is what lets the process exit on its own.

The Bun examples above run in a single process. When multiple services need to share one queue, run bunqueue as a standalone server instead:

ComparisonEmbedded modeServer mode
Best forSingle-process apps, serverlessMulti-process, microservices
Setupembedded: trueRun bunx bunqueue start, drop the option
ClientsBun only (in process)Node.js, Deno, Bun, Python, PHP, Go, Rust, Elixir, Cloudflare Workers

See the Server guide. All six official client SDKs speak the same protocol against the same queues, see the SDK guide. The server uses memory/SQLite by default; for several active brokers sharing one queue, configure the PostgreSQL 15–18 backend; 18.6 is recommended.

Less boilerplate. Bunqueue (Simple Mode) wraps Queue + Worker in one object with routes, middleware, and cron:

import { Bunqueue } from 'bunqueue/client';
const app = new Bunqueue('notifications', {
embedded: true,
routes: {
'send-email': async (job) => ({ sent: true }),
'send-sms': async (job) => ({ sent: true }),
},
concurrency: 10,
});
await app.add('send-email', { to: 'alice@example.com' });
await app.cron('daily-report', '0 9 * * *', { type: 'summary' });

See the Simple Mode guide.

Watch it live. The web dashboard shows queues, jobs, failures, crons, and workers. One command:

bunx bunqueue-dashboard

Try the live demo without installing anything, or read the dashboard guide for deployment and security.

Connect AI agents. bunqueue ships an MCP server with 79 tools, so agents like Claude can add jobs, manage crons, and monitor queues via natural language:

bun add -g bunqueue # provides the bunqueue-mcp binary
bun add -g @modelcontextprotocol/sdk # required by the MCP server only
claude mcp add bunqueue -- bunx --package=bunqueue bunqueue-mcp

Setup for Claude Desktop, Cursor, and Windsurf is in the MCP guide.

Orchestrate multi-step processes. The built-in workflow engine (Bun runtime) handles branching, parallel steps, rollback on failure, and human approvals:

import { Workflow, Engine } from 'bunqueue/workflow';
const flow = new Workflow('order')
.step('validate', async (ctx) => ({ ok: true }))
.step('charge', async (ctx) => ({ txId: 'tx_123' }))
.waitFor('manager-approval') // Pauses until you send a signal
.step('ship', async (ctx) => ({ shipped: true }));
const engine = new Engine({ embedded: true });
engine.register(flow);
await engine.start('order', { orderId: 'ORD-1' });

See the Workflow Engine guide.