- Docs
- SDKs & Integrations
- Hono
Background jobs for Hono.
Return the HTTP response now, do the slow work later. This guide wires bunqueue into Hono: enqueue from routes, process in a worker, check job status, shut down cleanly.
This guide shows how to run background jobs, work your server does after the HTTP response is sent, inside a Hono app. Everything runs in one process using bunqueue’s embedded mode, configured with dataPath so jobs are stored in a local SQLite file instead of a separate queue server.
Minimal working app
Section titled “Minimal working app”Copy, run with bun run app.ts, done:
import { Hono } from 'hono';import { Queue, Worker, shutdownManager } from 'bunqueue/client';
// Queue: where jobs wait. Worker: runs your function on each job.const storage = { embedded: true, dataPath: './data/bunq.db' } as const;const emails = new Queue('emails', storage);
new Worker( 'emails', async (job) => { console.log('sending to', job.data.to); // await sendEmail(job.data); // sendEmail: your own delivery function return { sent: true }; }, { ...storage, concurrency: 3 }); // 3 jobs in parallel
const app = new Hono();
app.post('/api/send-email', async (c) => { const body = await c.req.json(); const job = await emails.add('send', body, { attempts: 3, // retry up to 3 times on failure backoff: 5000, // exponential from a 5s base: ~10s, then ~20s between retries }); return c.json({ queued: true, jobId: job.id });});
process.on('SIGINT', () => { shutdownManager(); process.exit(0);});
export default app;The route responds immediately. The worker sends the email in the background and retries automatically if it throws.
Common tasks
Section titled “Common tasks”Let clients check job status
Section titled “Let clients check job status”Return the job id from the enqueue route, then expose a status endpoint. job.progress is the value your worker last passed to updateProgress() (by convention a 0-100 number, but an object is accepted too), returnvalue is what your worker returned, failedReason is the last error message:
app.get('/api/jobs/:id', async (c) => { const job = await emails.getJob(c.req.param('id')); if (!job) return c.json({ error: 'Job not found' }, 404);
return c.json({ id: job.id, name: job.name, progress: job.progress, result: job.returnvalue ?? null, error: job.failedReason ?? null, });});Report progress from the worker
Section titled “Report progress from the worker”// fetchData and renderPdf stand for your own functionsnew Worker( 'reports', async (job) => { await job.updateProgress(10, 'Fetching data'); const data = await fetchData(job.data); await job.updateProgress(80, 'Rendering PDF'); const url = await renderPdf(data); return { url }; }, { embedded: true, dataPath: './data/bunq.db' });Expose queue stats
Section titled “Expose queue stats”getJobCounts() is synchronous in embedded mode and returns counts per state (waiting, prioritized, active, completed, failed, delayed, paused, waiting-children):
app.get('/api/queues/emails/stats', (c) => c.json(emails.getJobCounts()));React to job results
Section titled “React to job results”// processor: your job handler, e.g. async (job) => ({ sent: true })const worker = new Worker('emails', processor, { embedded: true, dataPath: './data/bunq.db',});
worker.on('completed', (job, result) => console.log('done', job.id));worker.on('failed', (job, err) => console.error('failed', job.id, err.message));Share queues through typed middleware
Section titled “Share queues through typed middleware”For larger apps, put queues on Hono’s context so every route gets them typed:
import { Hono } from 'hono';import type { MiddlewareHandler } from 'hono';import { Queue } from 'bunqueue/client';
const queues = { emails: new Queue('emails', { embedded: true, dataPath: './data/bunq.db' }), reports: new Queue('reports', { embedded: true, dataPath: './data/bunq.db' }),};
type Env = { Variables: { queues: typeof queues } };
const queueMiddleware: MiddlewareHandler<Env> = async (c, next) => { c.set('queues', queues); await next();};
const app = new Hono<Env>();app.use('*', queueMiddleware);
app.post('/api/reports', async (c) => { const job = await c.get('queues').reports.add('generate', await c.req.json()); return c.json({ jobId: job.id });});Run workers in a separate process
Section titled “Run workers in a separate process”In production you often want the web server and workers to scale and restart independently. Start one bunqueue server as the sole owner of the SQLite file, then connect both processes over TCP:
bunqueue start --data-path ./data/bunq.dbUse the same connection in the Hono app’s Queue instances and the worker:
// In the Hono appimport { Queue } from 'bunqueue/client';
const connection = { host: '127.0.0.1', port: 6789 };const queues = { emails: new Queue('emails', { connection }), reports: new Queue('reports', { connection }),};// worker-process.ts (run with: bun run worker-process.ts)import { Worker } from 'bunqueue/client';
const connection = { host: '127.0.0.1', port: 6789 };
const worker = new Worker( 'emails', async (job) => { // ... process job return { success: true }; }, { connection, concurrency: 5 });
process.on('SIGTERM', async () => { await worker.close(); // waits for active jobs to finish process.exit(0);});Shut down cleanly
Section titled “Shut down cleanly”Close workers first (each close() waits for its active jobs), then release the embedded manager:
import { shutdownManager } from 'bunqueue/client';
// workers: every Worker this process createdasync function shutdown() { await Promise.all(workers.map((w) => w.close())); shutdownManager(); process.exit(0);}
process.on('SIGINT', shutdown);process.on('SIGTERM', shutdown);Gotchas
Section titled “Gotchas”- One
Queueinstance per queue name, created at startup. Creating queues inside handlers works but wastes memory and setup time on every request. - Bound long jobs with a
timeout. Jobs have no processing timeout by default; set one in the job options (for exampletimeout: 300_000for a 5 minute report job), and keep it above the longest legitimate run so a slow job is not failed early. - CPU-heavy processors block the event loop, the single thread Bun uses for all I/O. See CPU-Intensive Workers for yield patterns.