Tools
Tools: flashQ + Elysia & Hono.js: Background Jobs for Modern Bun Apps
2026-01-22
0 views
admin
Why This Stack? ## Elysia Integration ## Queue Configuration ## Elysia Plugin ## API Routes ## Hono.js Integration ## Queue Middleware ## Routes ## Worker (Shared) ## Advanced: Job Workflows ## Docker Compose Elysia and Hono.js are two of the fastest TypeScript frameworks out there. Combine them with flashQ and you get a stack capable of handling millions of background jobs with sub-millisecond API response times. Your API responds instantly. Heavy work happens in the background. Works with both Elysia and Hono: Chain jobs with dependencies: Both frameworks integrate seamlessly. Pick Elysia for pure Bun projects, Hono for portability. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
bun create elysia flashq-elysia
cd flashq-elysia
bun add flashq Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
bun create elysia flashq-elysia
cd flashq-elysia
bun add flashq CODE_BLOCK:
bun create elysia flashq-elysia
cd flashq-elysia
bun add flashq COMMAND_BLOCK:
// src/queue.ts
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; export async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), token: process.env.FLASHQ_TOKEN, }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI_PROCESSING: 'ai-processing',
} as const; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// src/queue.ts
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; export async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), token: process.env.FLASHQ_TOKEN, }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI_PROCESSING: 'ai-processing',
} as const; COMMAND_BLOCK:
// src/queue.ts
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; export async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), token: process.env.FLASHQ_TOKEN, }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI_PROCESSING: 'ai-processing',
} as const; CODE_BLOCK:
// src/plugins/flashq.ts
import { Elysia } from 'elysia';
import { getClient, QUEUES } from '../queue'; export const flashqPlugin = new Elysia({ name: 'flashq' }) .decorate('queue', { async push<T>(queue: string, data: T, options?: any) { const client = await getClient(); return client.push(queue, data, options); }, async getJob(jobId: string) { const client = await getClient(); return client.getJob(jobId); }, async waitForResult(jobId: string, timeout = 30000) { const client = await getClient(); return client.finished(jobId, timeout); }, QUEUES, }); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// src/plugins/flashq.ts
import { Elysia } from 'elysia';
import { getClient, QUEUES } from '../queue'; export const flashqPlugin = new Elysia({ name: 'flashq' }) .decorate('queue', { async push<T>(queue: string, data: T, options?: any) { const client = await getClient(); return client.push(queue, data, options); }, async getJob(jobId: string) { const client = await getClient(); return client.getJob(jobId); }, async waitForResult(jobId: string, timeout = 30000) { const client = await getClient(); return client.finished(jobId, timeout); }, QUEUES, }); CODE_BLOCK:
// src/plugins/flashq.ts
import { Elysia } from 'elysia';
import { getClient, QUEUES } from '../queue'; export const flashqPlugin = new Elysia({ name: 'flashq' }) .decorate('queue', { async push<T>(queue: string, data: T, options?: any) { const client = await getClient(); return client.push(queue, data, options); }, async getJob(jobId: string) { const client = await getClient(); return client.getJob(jobId); }, async waitForResult(jobId: string, timeout = 30000) { const client = await getClient(); return client.finished(jobId, timeout); }, QUEUES, }); COMMAND_BLOCK:
// src/index.ts
import { Elysia, t } from 'elysia';
import { flashqPlugin } from './plugins/flashq'; const app = new Elysia() .use(flashqPlugin) .post('/api/email', async ({ body, queue }) => { const job = await queue.push(queue.QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return { success: true, jobId: job.id }; }, { body: t.Object({ to: t.String({ format: 'email' }), subject: t.String({ minLength: 1 }), template: t.String(), data: t.Record(t.String(), t.Any()), }), }) .post('/api/generate', async ({ body, query, queue }) => { const job = await queue.push(queue.QUEUES.AI_PROCESSING, body, { priority: body.priority || 5, timeout: 120000, }); // Sync mode: wait for result if (query.sync === 'true') { const result = await queue.waitForResult(job.id, 60000); return { success: true, result }; } return { success: true, jobId: job.id }; }, { body: t.Object({ prompt: t.String({ minLength: 1 }), model: t.Optional(t.Union([ t.Literal('gpt-4'), t.Literal('claude-3'), ])), userId: t.String(), }), }) .get('/api/jobs/:id', async ({ params, queue }) => { const job = await queue.getJob(params.id); return job || { error: 'Job not found' }; }) .listen(3000); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// src/index.ts
import { Elysia, t } from 'elysia';
import { flashqPlugin } from './plugins/flashq'; const app = new Elysia() .use(flashqPlugin) .post('/api/email', async ({ body, queue }) => { const job = await queue.push(queue.QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return { success: true, jobId: job.id }; }, { body: t.Object({ to: t.String({ format: 'email' }), subject: t.String({ minLength: 1 }), template: t.String(), data: t.Record(t.String(), t.Any()), }), }) .post('/api/generate', async ({ body, query, queue }) => { const job = await queue.push(queue.QUEUES.AI_PROCESSING, body, { priority: body.priority || 5, timeout: 120000, }); // Sync mode: wait for result if (query.sync === 'true') { const result = await queue.waitForResult(job.id, 60000); return { success: true, result }; } return { success: true, jobId: job.id }; }, { body: t.Object({ prompt: t.String({ minLength: 1 }), model: t.Optional(t.Union([ t.Literal('gpt-4'), t.Literal('claude-3'), ])), userId: t.String(), }), }) .get('/api/jobs/:id', async ({ params, queue }) => { const job = await queue.getJob(params.id); return job || { error: 'Job not found' }; }) .listen(3000); COMMAND_BLOCK:
// src/index.ts
import { Elysia, t } from 'elysia';
import { flashqPlugin } from './plugins/flashq'; const app = new Elysia() .use(flashqPlugin) .post('/api/email', async ({ body, queue }) => { const job = await queue.push(queue.QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return { success: true, jobId: job.id }; }, { body: t.Object({ to: t.String({ format: 'email' }), subject: t.String({ minLength: 1 }), template: t.String(), data: t.Record(t.String(), t.Any()), }), }) .post('/api/generate', async ({ body, query, queue }) => { const job = await queue.push(queue.QUEUES.AI_PROCESSING, body, { priority: body.priority || 5, timeout: 120000, }); // Sync mode: wait for result if (query.sync === 'true') { const result = await queue.waitForResult(job.id, 60000); return { success: true, result }; } return { success: true, jobId: job.id }; }, { body: t.Object({ prompt: t.String({ minLength: 1 }), model: t.Optional(t.Union([ t.Literal('gpt-4'), t.Literal('claude-3'), ])), userId: t.String(), }), }) .get('/api/jobs/:id', async ({ params, queue }) => { const job = await queue.getJob(params.id); return job || { error: 'Job not found' }; }) .listen(3000); CODE_BLOCK:
bun create hono@latest flashq-hono
cd flashq-hono
bun add flashq zod @hono/zod-validator Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
bun create hono@latest flashq-hono
cd flashq-hono
bun add flashq zod @hono/zod-validator CODE_BLOCK:
bun create hono@latest flashq-hono
cd flashq-hono
bun add flashq zod @hono/zod-validator COMMAND_BLOCK:
// src/middleware/queue.ts
import { createMiddleware } from 'hono/factory';
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI: 'ai-processing' } as const; export const queueMiddleware = createMiddleware(async (c, next) => { const flashq = await getClient(); c.set('queue', { push: (name, data, options) => flashq.push(name, data, options), getJob: (id) => flashq.getJob(id), finished: (id, timeout) => flashq.finished(id, timeout), }); await next();
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// src/middleware/queue.ts
import { createMiddleware } from 'hono/factory';
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI: 'ai-processing' } as const; export const queueMiddleware = createMiddleware(async (c, next) => { const flashq = await getClient(); c.set('queue', { push: (name, data, options) => flashq.push(name, data, options), getJob: (id) => flashq.getJob(id), finished: (id, timeout) => flashq.finished(id, timeout), }); await next();
}); COMMAND_BLOCK:
// src/middleware/queue.ts
import { createMiddleware } from 'hono/factory';
import { FlashQ } from 'flashq'; let client: FlashQ | null = null; async function getClient(): Promise<FlashQ> { if (!client) { client = new FlashQ({ host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }); await client.connect(); } return client;
} export const QUEUES = { EMAIL: 'email', AI: 'ai-processing' } as const; export const queueMiddleware = createMiddleware(async (c, next) => { const flashq = await getClient(); c.set('queue', { push: (name, data, options) => flashq.push(name, data, options), getJob: (id) => flashq.getJob(id), finished: (id, timeout) => flashq.finished(id, timeout), }); await next();
}); COMMAND_BLOCK:
// src/index.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { queueMiddleware, QUEUES } from './middleware/queue'; const app = new Hono();
app.use('/api/*', queueMiddleware); const emailSchema = z.object({ to: z.string().email(), subject: z.string().min(1), template: z.string(), data: z.record(z.any()),
}); app.post('/api/email', zValidator('json', emailSchema), async (c) => { const body = c.req.valid('json'); const queue = c.get('queue'); const job = await queue.push(QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return c.json({ success: true, jobId: job.id });
}); app.get('/api/jobs/:id', async (c) => { const queue = c.get('queue'); const job = await queue.getJob(c.req.param('id')); return job ? c.json(job) : c.json({ error: 'Not found' }, 404);
}); export default { port: 3000, fetch: app.fetch }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// src/index.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { queueMiddleware, QUEUES } from './middleware/queue'; const app = new Hono();
app.use('/api/*', queueMiddleware); const emailSchema = z.object({ to: z.string().email(), subject: z.string().min(1), template: z.string(), data: z.record(z.any()),
}); app.post('/api/email', zValidator('json', emailSchema), async (c) => { const body = c.req.valid('json'); const queue = c.get('queue'); const job = await queue.push(QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return c.json({ success: true, jobId: job.id });
}); app.get('/api/jobs/:id', async (c) => { const queue = c.get('queue'); const job = await queue.getJob(c.req.param('id')); return job ? c.json(job) : c.json({ error: 'Not found' }, 404);
}); export default { port: 3000, fetch: app.fetch }; COMMAND_BLOCK:
// src/index.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { queueMiddleware, QUEUES } from './middleware/queue'; const app = new Hono();
app.use('/api/*', queueMiddleware); const emailSchema = z.object({ to: z.string().email(), subject: z.string().min(1), template: z.string(), data: z.record(z.any()),
}); app.post('/api/email', zValidator('json', emailSchema), async (c) => { const body = c.req.valid('json'); const queue = c.get('queue'); const job = await queue.push(QUEUES.EMAIL, body, { attempts: 5, backoff: 5000, }); return c.json({ success: true, jobId: job.id });
}); app.get('/api/jobs/:id', async (c) => { const queue = c.get('queue'); const job = await queue.getJob(c.req.param('id')); return job ? c.json(job) : c.json({ error: 'Not found' }, 404);
}); export default { port: 3000, fetch: app.fetch }; COMMAND_BLOCK:
// worker/index.ts
import { Worker } from 'flashq';
import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); const emailWorker = new Worker('email', async (job) => { const { to, subject, template, data } = job.data; const html = renderTemplate(template, data); const result = await resend.emails.send({ from: '[email protected]', to, subject, html, }); return { emailId: result.id };
}, { connection: { host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }, concurrency: 10,
}); emailWorker.on('completed', (job) => { console.log(`✓ Job ${job.id} completed`);
}); emailWorker.on('failed', (job, error) => { console.error(`✗ Job ${job.id} failed: ${error.message}`);
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
// worker/index.ts
import { Worker } from 'flashq';
import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); const emailWorker = new Worker('email', async (job) => { const { to, subject, template, data } = job.data; const html = renderTemplate(template, data); const result = await resend.emails.send({ from: '[email protected]', to, subject, html, }); return { emailId: result.id };
}, { connection: { host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }, concurrency: 10,
}); emailWorker.on('completed', (job) => { console.log(`✓ Job ${job.id} completed`);
}); emailWorker.on('failed', (job, error) => { console.error(`✗ Job ${job.id} failed: ${error.message}`);
}); COMMAND_BLOCK:
// worker/index.ts
import { Worker } from 'flashq';
import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); const emailWorker = new Worker('email', async (job) => { const { to, subject, template, data } = job.data; const html = renderTemplate(template, data); const result = await resend.emails.send({ from: '[email protected]', to, subject, html, }); return { emailId: result.id };
}, { connection: { host: process.env.FLASHQ_HOST || 'localhost', port: parseInt(process.env.FLASHQ_PORT || '6789'), }, concurrency: 10,
}); emailWorker.on('completed', (job) => { console.log(`✓ Job ${job.id} completed`);
}); emailWorker.on('failed', (job, error) => { console.error(`✗ Job ${job.id} failed: ${error.message}`);
}); COMMAND_BLOCK:
app.post('/api/pipeline', async (c) => { const client = await getClient(); // Step 1: Extract const extractJob = await client.push('extract', { documentUrl: body.url }); // Step 2: Summarize (waits for extract) const summarizeJob = await client.push('summarize', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); // Step 3: Embed (waits for extract) const embedJob = await client.push('embed', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); return c.json({ jobs: { extract: extractJob.id, summarize: summarizeJob.id, embed: embedJob.id, }, });
}); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
app.post('/api/pipeline', async (c) => { const client = await getClient(); // Step 1: Extract const extractJob = await client.push('extract', { documentUrl: body.url }); // Step 2: Summarize (waits for extract) const summarizeJob = await client.push('summarize', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); // Step 3: Embed (waits for extract) const embedJob = await client.push('embed', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); return c.json({ jobs: { extract: extractJob.id, summarize: summarizeJob.id, embed: embedJob.id, }, });
}); COMMAND_BLOCK:
app.post('/api/pipeline', async (c) => { const client = await getClient(); // Step 1: Extract const extractJob = await client.push('extract', { documentUrl: body.url }); // Step 2: Summarize (waits for extract) const summarizeJob = await client.push('summarize', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); // Step 3: Embed (waits for extract) const embedJob = await client.push('embed', { sourceJobId: extractJob.id, }, { depends_on: [extractJob.id], }); return c.json({ jobs: { extract: extractJob.id, summarize: summarizeJob.id, embed: embedJob.id, }, });
}); CODE_BLOCK:
version: '3.8' services: flashq: image: ghcr.io/egeominotti/flashq:latest ports: - "6789:6789" environment: - DATABASE_URL=postgres://flashq:flashq@postgres:5432/flashq depends_on: - postgres postgres: image: postgres:16-alpine environment: - POSTGRES_USER=flashq - POSTGRES_PASSWORD=flashq - POSTGRES_DB=flashq api: build: . ports: - "3000:3000" environment: - FLASHQ_HOST=flashq - FLASHQ_PORT=6789 depends_on: - flashq worker: build: context: . dockerfile: Dockerfile.worker environment: - FLASHQ_HOST=flashq depends_on: - flashq deploy: replicas: 3 Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
version: '3.8' services: flashq: image: ghcr.io/egeominotti/flashq:latest ports: - "6789:6789" environment: - DATABASE_URL=postgres://flashq:flashq@postgres:5432/flashq depends_on: - postgres postgres: image: postgres:16-alpine environment: - POSTGRES_USER=flashq - POSTGRES_PASSWORD=flashq - POSTGRES_DB=flashq api: build: . ports: - "3000:3000" environment: - FLASHQ_HOST=flashq - FLASHQ_PORT=6789 depends_on: - flashq worker: build: context: . dockerfile: Dockerfile.worker environment: - FLASHQ_HOST=flashq depends_on: - flashq deploy: replicas: 3 CODE_BLOCK:
version: '3.8' services: flashq: image: ghcr.io/egeominotti/flashq:latest ports: - "6789:6789" environment: - DATABASE_URL=postgres://flashq:flashq@postgres:5432/flashq depends_on: - postgres postgres: image: postgres:16-alpine environment: - POSTGRES_USER=flashq - POSTGRES_PASSWORD=flashq - POSTGRES_DB=flashq api: build: . ports: - "3000:3000" environment: - FLASHQ_HOST=flashq - FLASHQ_PORT=6789 depends_on: - flashq worker: build: context: . dockerfile: Dockerfile.worker environment: - FLASHQ_HOST=flashq depends_on: - flashq deploy: replicas: 3 - Elysia: Bun-native, end-to-end type safety with t schemas
- Hono.js: Multi-runtime (Bun, Node, Cloudflare Workers), Zod validation
- flashQ: 1.9M jobs/sec, BullMQ-compatible API, Rust-powered - flashQ GitHub
how-totutorialguidedev.toaimlllmgptdockernodessldatabasegitgithub