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.

Create a queue, add jobs, process them with a Worker, and turn on persistence. On Bun everything runs embedded in a single process with zero configuration; from any other language, start the server once and connect.

Install the client for your runtime (see the SDK guide), save the snippet as a file, run it. On Bun the queue runs embedded in your process; in every other language, start the server once with bunx bunqueue start first.

import { Queue, Worker } from 'bunqueue/client';
// The queue: where you put jobs
const queue = new Queue('emails', { embedded: true });
// The worker: pulls jobs and runs your function on each one
const worker = new Worker(
'emails',
async (job) => {
console.log(`Sending "${job.data.subject}" to ${job.data.to}`);
return { sent: true };
},
{ embedded: true }
);
// Add a job
await queue.add('welcome', { to: 'user@example.com', subject: 'Welcome!' });

The snippets above are the body of a program, not a whole file: drop each one into your project’s entry point, then run it. The worker keeps running until you stop it with Ctrl-C:

Terminal window
bun run app.ts # Bun
node app.js # Node.js (needs "type": "module" in package.json)
deno run -A app.ts # Deno
python app.py # Python
php app.php # PHP
go run . # Go
cargo run # Rust
mix run --no-halt # Elixir

The worker prints one line per job:

Sending "Welcome!" to user@example.com # Bun, Node.js / Deno
Sending Welcome! to user@example.com # Python, PHP, Go, Elixir
Sending Map([(String(Utf8String { s: Ok("to") }), ...)]) # Rust: `{:?}` on the decoded msgpack value

On Bun, embedded: true means the queue runs inside your process, no server needed. Every other client talks TCP to the server (bunx bunqueue start) and defaults to localhost:6789.

// 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, // Retry up to 3 times 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:

Terminal window
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);
});

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:

Terminal window
bunx bunqueue-dashboard

Try the live demo without installing anything.

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

Terminal window
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.