Skip to content

Quick Start — Build Your First Bun Job Queue in 5 Minutes

This guide will get you up and running with bunqueue in 5 minutes.

bunqueue supports two deployment modes:

Embedded ModeTCP Server Mode
Best forSingle-process apps, serverlessMulti-process, microservices
SetupZero configRun bunqueue start first
Option neededembedded: trueNone (default)
PersistenceDATA_PATH env var--data-path flag

This guide covers Embedded Mode (most common). For TCP Server Mode, see Server Guide.

import { Queue } from 'bunqueue/client';
// Create a typed queue
interface EmailJob {
to: string;
subject: string;
body: string;
}
const emailQueue = new Queue<EmailJob>('emails', { embedded: true });
// Add a single job
const job = await emailQueue.add('send-email', {
to: 'user@example.com',
subject: 'Welcome!',
body: 'Thanks for signing up.'
});
console.log(`Job created: ${job.id}`);
// Add with options
await emailQueue.add('send-email', data, {
priority: 10, // Higher = processed first
delay: 5000, // Wait 5 seconds before processing
attempts: 3, // Retry up to 3 times
backoff: 1000, // Wait 1 second between retries
});
// Add multiple jobs (batch optimized)
await emailQueue.addBulk([
{ name: 'send-email', data: { to: 'a@test.com', subject: 'Hi', body: '...' } },
{ name: 'send-email', data: { to: 'b@test.com', subject: 'Hi', body: '...' } },
]);
import { Worker } from 'bunqueue/client';
const worker = new Worker<EmailJob>('emails', async (job) => {
console.log(`Processing: ${job.name}`);
// Update progress
await job.updateProgress(50, 'Sending email...');
// Do the work
await sendEmail(job.data);
// Log messages
await job.log('Email sent successfully');
// Return a result
return { sent: true, timestamp: Date.now() };
}, {
embedded: true, // Required for embedded mode
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}%`);
});
worker.on('active', (job) => {
console.log(`Job ${job.id} started`);
});
import { Queue, Worker, shutdownManager } from 'bunqueue/client';
interface EmailJob {
to: string;
subject: string;
}
// Producer - must have embedded: true
const queue = new Queue<EmailJob>('emails', { embedded: true });
// Add some jobs
await queue.add('welcome', { to: 'new@user.com', subject: 'Welcome!' });
await queue.add('newsletter', { to: 'sub@user.com', subject: 'News' });
// Consumer - must have embedded: true
const worker = new Worker<EmailJob>('emails', async (job) => {
console.log(`Sending ${job.data.subject} to ${job.data.to}`);
await job.updateProgress(100);
return { sent: true };
}, { embedded: true, concurrency: 3 });
worker.on('completed', (job) => {
console.log(`✓ ${job.id}`);
});
// Graceful shutdown
process.on('SIGINT', async () => {
await worker.close();
shutdownManager();
process.exit(0);
});

To persist jobs across restarts, pass dataPath in the constructor or set DATA_PATH before importing:

import { Queue, Worker } from 'bunqueue/client';
// Option 1: Pass dataPath directly (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
// DATA_PATH=./data/bunqueue.db bun run app.ts

Want less boilerplate? Bunqueue wraps Queue + Worker in a single object with routes, middleware, cron, and more:

import { Bunqueue } from 'bunqueue/client';
const app = new Bunqueue('notifications', {
embedded: true,
routes: {
'send-email': async (job) => {
await sendEmail(job.data.to);
return { sent: true };
},
'send-sms': async (job) => {
await sendSMS(job.data.to);
return { sent: true };
},
},
concurrency: 10,
});
// Middleware (wraps every job)
app.use(async (job, next) => {
const start = Date.now();
const result = await next();
console.log(`${job.name}: ${Date.now() - start}ms`);
return result;
});
// Cron jobs
await app.cron('daily-report', '0 9 * * *', { type: 'summary' });
// Add jobs
await app.add('send-email', { to: 'alice@example.com' });
// Graceful shutdown
await app.close();

Simple Mode also includes circuit breaker, batch processing, TTL, priority aging, deduplication, and debouncing. See Simple Mode guide for the full reference.

bunqueue includes a native MCP server with 73 tools. AI agents can schedule tasks, manage pipelines, and monitor queues via natural language — no code needed.

Terminal window
# Claude Code
claude mcp add bunqueue -- bunx bunqueue-mcp
// Claude Desktop / Cursor / Windsurf
{
"mcpServers": {
"bunqueue": {
"command": "bunx",
"args": ["bunqueue-mcp"]
}
}
}

Once connected, agents can add jobs, manage crons, retry failures, set rate limits, and monitor everything. See MCP Server guide for the full reference.

Need to orchestrate multi-step processes? bunqueue includes a built-in Workflow Engine with branching, saga compensation, and human-in-the-loop signals:

import { Workflow, Engine } from 'bunqueue/workflow';
const flow = new Workflow('order-pipeline')
.step('validate', async (ctx) => {
const { orderId } = ctx.input as { orderId: string };
return { orderId };
})
.step('charge', async (ctx) => {
return { txId: 'tx_123' };
}, {
compensate: async () => {
// Auto-rollback if a later step fails
await refundPayment('tx_123');
},
})
.waitFor('manager-approval') // Pauses until signal received
.step('ship', async (ctx) => {
const approval = ctx.signals['manager-approval'];
return { shipped: true };
});
const engine = new Engine({ embedded: true });
engine.register(flow);
const run = await engine.start('order-pipeline', { orderId: 'ORD-1' });
// Later, when the manager approves:
await engine.signal(run.id, 'manager-approval', { approved: true });

Built on top of bunqueue’s Queue and Worker — no new infrastructure. Workflow Engine guide for the full reference.