Cover image for I got tired of running Redis for one background job, so I built a SQLite job queue

Serhat

Every side project I've shipped in the last few years has hit the same wall at roughly the same point. The app works, it runs on a single cheap VPS, and then I need to send an email after the request returns. Or resize an image. Or run something every night at 3am.

The standard answer in Node land is BullMQ, which means Redis. And Redis is great. But now my "one small app on one small server" has a second service to install, monitor, secure, and remember exists when the box reboots. All so I can send an email five seconds later.

The third time I caught myself provisioning Redis for a queue that would see maybe two hundred jobs a day, I stopped and wrote vardiya instead.

The idea

If your whole app lives on one machine, you already have a durable, transactional store sitting right there: the disk. SQLite in WAL mode handles concurrent readers and a single writer perfectly well, and a job queue is mostly just "one writer claims rows atomically."

So vardiya keeps the entire queue in a SQLite file. One runtime dependency (better-sqlite3). Cron parsing, backoff math, and id generation are all in-house, so there's no dependency tree to audit.

import { Vardiya } from "vardiya";

const v = new Vardiya({ databasePath: "./jobs.sqlite" });
await v.init();

await v.enqueue("email", { to: "[email protected]" }, { delayMs: 5_000 });

const worker = v.createWorker({ concurrency: 4 });
worker.process("email", async (job) => sendEmail(job.payload));
await worker.start();

Enter fullscreen mode Exit fullscreen mode

That's the whole setup. No broker URL, no connection retries, no second process. When your app dies, the file is still there, and so are your jobs.

What it actually covers

I didn't want a toy that falls over the moment you use it seriously, so the boring-but-necessary parts are in:

  • Retries with fixed or exponential backoff, optional jitter, and a dead-letter state when maxAttempts runs out
  • Delayed jobs, priorities, and custom jobId for dedup when your producer might fire twice
  • Repeatable jobs via 5-field cron (plus @hourly, @daily, @weekly, @monthly)
  • Heartbeats and stalled-job reclaim, so a crashed worker doesn't strand jobs in "active" forever
  • Atomic claim via a single UPDATE ... RETURNING, so multiple worker processes on the same file won't grab the same job
  • Typed events for everything (job:completed, job:failed, job:dead, ...)

On my machine the bench does around 13k enqueues/sec and roughly 5k processed jobs/sec end-to-end. Your disk will disagree with my disk, so run npm run bench yourself before quoting numbers.

The honest part

vardiya is at-least-once, and I say so in the README instead of burying it. A job can run twice if a worker dies after doing the work but before recording completion. Every crash-safe queue has this property; the ones that claim exactly-once are just moving the problem into your side effects. Write idempotent handlers, use jobId as a dedup token, and you're fine.

Also: SQLite is a single-writer database. Multiple processes on one host sharing a local file works. A fleet of app servers fighting over a network mount will not, and I'm not going to pretend otherwise. If you need many machines pulling from one logical queue, BullMQ or pg-boss are the right tools and I'd use them myself. Same if you want dashboards, rate-limit groups, or sandboxed processors. Those are product features other projects spent years on, and vardiya is deliberately just the queue core.

Why "vardiya"?

It's Turkish for "work shift." Workers clocking in, picking up jobs, clocking out. It felt right.

Try it

npm install vardiya

Enter fullscreen mode Exit fullscreen mode

Requires Node >=22. The repo has examples, the full API reference, and a comparison table against BullMQ / pg-boss / bee-queue if you want the details: github.com/Zulwatha/vardiya

It's MIT licensed and young. Issues and PRs are very welcome, especially war stories from anyone who has pushed SQLite queues harder than I have. And if the "one VPS, no Redis" shape matches your app, I'd genuinely like to hear whether it holds up for you.