RecipesQueue

Background Jobs / Deferred Work

Run work after the response without blocking the request — and know when you actually need a real queue.

NextRush does not ship a job-queue package — there is no @nextrush/queue among its 35 packages (Packages directory). For a single-process app, setImmediate/an async fire-and-forget call after ctx.json() covers "send a welcome email, don't make the client wait for it." For anything that must survive a crash or run across multiple instances, reach for a real queue (BullMQ + Redis, or a managed service) — this recipe shows the boundary, not a replacement for one.

Solution

src/jobs/in-process-queue.ts
type Job = () => Promise<void>;

/**
 * Minimal in-process, in-memory job queue. Runs one job at a time, in order.
 * Jobs are lost on crash/restart — this is a request-decoupling tool, not a
 * durability guarantee. Reach for BullMQ/Redis (or a managed queue) the
 * moment "survives a restart" or "runs across multiple instances" matters.
 */
class InProcessQueue {
  private queue: Job[] = [];
  private running = false;

  enqueue(job: Job): void {
    this.queue.push(job);
    void this.drain();
  }

  private async drain(): Promise<void> {
    if (this.running) return;
    this.running = true;

    while (this.queue.length > 0) {
      const job = this.queue.shift()!;
      try {
        await job();
      } catch (err) {
        console.error('[jobs] job failed', err);
      }
    }

    this.running = false;
  }
}

export const jobs = new InProcessQueue();
src/index.ts
import { createApp, listen } from 'nextrush';
import { jobs } from './jobs/in-process-queue';
import { sendWelcomeEmail } from './email';

const app = createApp();

app.post('/signup', (ctx) => {
  const { email } = ctx.body as { email: string };

  // Respond immediately; the email send happens after this handler returns.
  jobs.enqueue(() => sendWelcomeEmail(email));

  ctx.status = 202;
  ctx.json({ status: 'accepted' });
});

listen(app, 8080);

ctx.next() is irrelevant here — this is a route handler, not middleware — the point is that jobs.enqueue() returns synchronously and the response is sent before sendWelcomeEmail resolves. 202 Accepted (rather than 200/201) signals to the caller that the work is deferred, not necessarily done by the time the response arrives. See Context for ctx.status/ctx.json.

Not durable

InProcessQueue holds jobs in memory. A crash, redeploy, or pm2 restart between enqueue() and the job running loses that job silently. If losing a queued email is unacceptable, this pattern is the wrong tool — use a persisted queue instead.

Try It

curl -i -X POST http://localhost:8080/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"new-user@example.com"}'

Expected result: 202 Accepted with {"status":"accepted"} returned immediately, followed shortly after (in the server logs) by the welcome email actually sending — the client never waits for it.

  • Packages directory — confirms no queue/job package ships with NextRush
  • Contextctx.status, ctx.json
  • Reliability — graceful shutdown considerations if you adopt this pattern (in-flight jobs on SIGTERM)
Was this helpful?

On this page