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.
Why This Stack?
| Elysia | Hono.js | flashQ | |
|---|---|---|---|
| Performance | ~2.5M req/sec | ~1.5M req/sec | ~1.9M jobs/sec |
| Runtime | Bun-native | Multi-runtime | Rust-powered |
| Type Safety | End-to-end | Full TypeScript | Typed SDK |
Your API responds instantly. Heavy work happens in the background.
Elysia Integration
Setup
bun create elysia flashq-elysia
cd flashq-elysia
bun add flashq
Queue Configuration
// 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;
Elysia Plugin
// 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,
});
API Routes
// 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);
Hono.js Integration
Setup
bun create hono@latest flashq-hono
cd flashq-hono
bun add flashq zod @hono/zod-validator
Queue Middleware
// 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();
});
Routes
// 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 };
Worker (Shared)
Works with both Elysia and Hono:
// 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: 'noreply@yourdomain.com',
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}`);
});
Advanced: Job Workflows
Chain jobs with dependencies:
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,
},
});
});
Docker Compose
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
TL;DR
-
Elysia: Bun-native, end-to-end type safety with
tschemas - Hono.js: Multi-runtime (Bun, Node, Cloudflare Workers), Zod validation
- flashQ: 1.9M jobs/sec, BullMQ-compatible API, Rust-powered
Both frameworks integrate seamlessly. Pick Elysia for pure Bun projects, Hono for portability.
Links:
Top comments (0)