flexmq is a lightweight TypeScript job queue for Node.js with one API across in-memory, Redis, and Postgres backends.
The project is organized as a monorepo with three packages:
flexmq- core queue and worker implementation (in-memory storage included)@flexmq/redis- Redis storage adapter for production-style deployments@flexmq/postgres- Postgres storage adapter for SQL-backed deployments- One queue API across memory, Redis, and Postgres
- Built for real background work: retries, backpressure, concurrency, leases, and recovery
- Good fit for SaaS apps, internal tools, APIs, webhooks, email, and report generation
- Lets teams stay on existing infrastructure instead of forcing a Redis-only choice
Use flexmq when you want:
- a queue library inside your TypeScript application, not a separate platform
- a smooth path from local development to production
- durable, at-least-once job processing with clear delivery semantics
- the option to run on Redis or Postgres without rewriting your app code
flexmq is especially useful for:
- email and notification pipelines
- webhooks and retryable outbound API calls
- async document or report generation
- background data sync and enrichment jobs
- horizontally scaled worker processes
| Backend | Best for | Why use it |
|---|---|---|
| In-memory | local development, tests, simple single-process apps | zero setup, fastest way to start |
| Redis | queue-heavy production workloads | cross-process coordination, producer wakeups, terminal-state waiting |
| Postgres | teams already standardized on SQL | durable jobs without adding Redis infrastructure |
Pluggable storage: start with memory, switch to Redis or Postgres laterBackpressure control: block producers, drop oldest, drop newest, or throw immediatelyLease-based claims: protect processing ownership withclaimTokenRecovery model: recover expired claims and keep jobs moving after worker lossCross-process waiting: supported by Redis and Postgres adaptersTypeScript-first: strict-mode codebase, typed queue payloads, typed adapters
import { Queue, Worker } from "flexmq";
type EmailJob = { to: string; subject: string };
const queue = new Queue<EmailJob>("emails", { capacity: 1000 });
const worker = new Worker<EmailJob>("emails", {
concurrency: 2,
processor: async (job) => {
console.log(`Sending email to ${job.payload.to}`);
},
});
async function main() {
await queue.connect();
await queue.add({ to: "user@example.com", subject: "Welcome" }, { maxAttempts: 3 });
await queue.add({ to: "ops@example.com", subject: "Daily report" }, { maxAttempts: 5 });
await worker.start();
}
main().catch(console.error);npm install flexmqnpm install flexmq @flexmq/redis ioredisnpm install flexmq @flexmq/postgres pgimport { Queue } from "flexmq";
const queue = new Queue("tasks", { capacity: 1000 });import { Queue } from "flexmq";
import { RedisStorageAdapter } from "@flexmq/redis";
const storage = new RedisStorageAdapter({
host: "127.0.0.1",
port: 6379,
password: process.env.REDIS_PASSWORD,
capacity: 10000,
});
const queue = new Queue("tasks", { storage });import { Queue } from "flexmq";
import { PostgresStorageAdapter } from "@flexmq/postgres";
const storage = new PostgresStorageAdapter({
host: "127.0.0.1",
port: 5432,
user: "postgres",
password: process.env.PGPASSWORD,
database: "postgres",
capacity: 10000,
});
await storage.connect();
await storage.ensureSchema();
const queue = new Queue("tasks", { storage });flexmq is built for at-least-once delivery.
- workers claim jobs atomically from
pending - claimed jobs move to
processingwithworkerId,claimedAt,leaseUntil, andclaimToken - only the active claim owner can
complete,fail,retry, orrenewLease - expired claims can be recovered back to
pending - processors should be idempotent when duplicate delivery matters
When the queue reaches capacity, choose the behavior that matches your workload:
BackpressureStrategy.BLOCK_PRODUCERBackpressureStrategy.DROP_OLDESTBackpressureStrategy.DROP_NEWESTBackpressureStrategy.ERROR
- set
maxAttemptsper job - failed jobs retry with exponential delay
- exhausted jobs move to
failed
- workers claim jobs atomically from
pending - a claimed job moves to
processingand getsworkerId,claimedAt,leaseUntil, andclaimToken - only the worker holding the active
claimTokenmaycomplete,fail,retry, orrenewLease - expired claims are recoverable back to
pending
queue.waitForTerminalState(jobId, timeoutMs) waits until a job becomes completed or failed.
Redis and Postgres adapters support waiting across processes.
Many teams already know whether they want Redis or Postgres. Others do not want to decide too early.
flexmq gives you a small, typed queue API now and lets you evolve the storage choice later:
- use memory for fast local development
- use Redis when you want classic queue infrastructure
- use Postgres when your team prefers durable jobs on existing SQL systems
flexmq- core queue and worker implementation with in-memory storage@flexmq/redis- Redis storage adapter for multi-process deployments@flexmq/postgres- Postgres storage adapter for SQL-backed deployments
The repository includes scripts and docs for publishing benchmark and reliability results.
- benchmark guide:
docs/BENCHMARKS.md - reliability guide:
docs/RELIABILITY.md
Run them from the repo root:
npm run bench:memory
npm run bench:redis
npm run stress:recovery
npm run stress:block-producerQueue events:
queue:connectedqueue:disconnectedjob:addedjob:dropped
Worker events:
worker:startedworker:stoppedjob:processingjob:completedjob:retryjob:failedjob:lost-claimjobs:promotedjobs:recoveredworker:error
Install dependencies:
npm installBuild all packages:
npm run buildRun tests:
npm test- TypeScript strict mode enabled
- Jest + ts-jest test setup
- npm workspaces monorepo
Useful commands:
npm run test:coverage
npm run lint
npm run format:check
npm run cleanMIT