Skip to main content
Alpha

Choose a queue provider

Scope: how to pick the right backend for @netscript/queue — the in-memory test adapter, the zero-config Deno KV default, Redis, RabbitMQ (AMQP), and PostgreSQL — and how to either let auto-discovery select one or pin one explicitly with provider + connection. This is the decision recipe; for the enqueue/consume/cron mechanics see Queue / KV / cron.

A NetScript queue is provider-agnostic by design: the same createQueue("jobs") runs on an in-memory adapter in a unit test, on Deno KV on your laptop, and on a real broker under Aspire — without touching the call site. The only thing that changes is which backend the factory resolves. This recipe is about making that choice deliberately.

Prerequisites

Before you start
NameTypeDescription
A NetScript workspace netscript init Created per the Quickstart. Import @netscript/queue from any workspace member.
aspire startning (for real backends) cd aspire && aspire start Aspire provisions Postgres, Redis/Garnet, and any AMQP broker BEFORE your service connects. The in-memory and Deno KV adapters need nothing extra; Redis, RabbitMQ, and the Postgres queue all expect Aspire up first.
Deno KV unstable flag --unstable-kv The Deno KV backend (the auto-discovery fallback) uses Deno KV. The scaffold's deno.json sets unstable: ['raw-imports', 'kv']; add --unstable-kv to any ad-hoc deno run/check that touches the queue.

The five backends at a glance

There are four selectable production providers in the QueueProvider enum — Deno KV, Redis, RabbitMQ, and PostgreSQL — plus the MemoryQueueAdapter from @netscript/queue/testing for tests and examples. Pick by the row that matches your deployment, then jump to the matching step.

Queue backends in @netscript/queue
BackendHow you select itReach for it when
MemoryQueueAdapter new MemoryQueueAdapter() from @netscript/queue/testing (never auto-discovered) Unit tests and isolated examples — volatile, in-process, zero setup; data is lost on restart.
Deno KV (provider: 'deno-kv') Auto-discovery fallback, or pin QueueProvider.DenoKv Local dev and single-instance apps with no broker; durable to the Deno KV store. Needs --unstable-kv.
Redis (provider: 'redis') Auto-discovered when a Redis/Garnet connection is present; or pin QueueProvider.Redis High-throughput production with a Redis/Garnet resource already provisioned by Aspire.
RabbitMQ (provider: 'rabbitmq') Auto-discovered first when an AMQP broker is present; or pin QueueProvider.RabbitMQ Broker-grade routing and reliability via Fedify's AMQP adapter; the top of the auto-discovery probe.
PostgreSQL (provider: 'postgres') Explicit only — pass QueueProvider.Postgres; never auto-discovered You already run Postgres and want one fewer moving part, or want the queue to share your transactional store. Row-claim via FOR UPDATE SKIP LOCKED.

Step 1 — Default: let auto-discovery pick

If you pass no provider, the factory probes the Aspire environment and selects the first available backend in a fixed order. Write provider-neutral code and let the environment decide:

// queue.ts
import { createTypedQueue } from "@netscript/queue";
import { z } from "zod";

const JobSchema = z.object({ id: z.string(), kind: z.string() });

// No provider → auto-discovery. Probe order: RabbitMQ → Redis → Deno KV.
export const jobs = createTypedQueue("jobs", JobSchema);

The probe order is RabbitMQ (AMQP) → Redis → Deno KV. Deno KV is the always-available fallback when no broker is discoverable, so the same code runs unchanged from laptop to production as you provision real backends under Aspire.

Step 2 — Pin a provider explicitly

To take auto-discovery out of the loop, pass provider from the QueueProvider enum. Connection details go under connection.<provider>; every URL is optional and falls back to Aspire service discovery when omitted.

// queue.ts
import { createQueue, QueueProvider } from "@netscript/queue";

const jobs = createQueue("jobs", {
  provider: QueueProvider.Redis,
  connection: {
    redis: {
      // url optional → falls back to getRedisConnectionFromEnv() / Aspire.
      url: Deno.env.get("REDIS_URL"),
      // options are passed through to ioredis.
      options: {},
    },
  },
});
// queue.ts
import { createQueue, QueueProvider } from "@netscript/queue";

const jobs = createQueue("jobs", {
  provider: QueueProvider.RabbitMQ,
  connection: {
    rabbitmq: {
      // url optional → derived from Aspire's rabbitmq service (amqp://).
      url: Deno.env.get("AMQP_URL"),
      // queueName defaults to the queue's logical name.
      queueName: "jobs",
    },
  },
});
// queue.ts
import { createQueue, QueueProvider } from "@netscript/queue";

const jobs = createQueue("jobs", {
  provider: QueueProvider.DenoKv,
  connection: {
    denoKv: {
      // path optional → Aspire discovery or the default shared Deno KV.
      path: Deno.env.get("DENO_KV_URL"),
      verbose: false,
    },
  },
});

Step 3 — Select the PostgreSQL backend

The PostgreSQL provider gives you a SQL-durable queue with row-claim semantics (FOR UPDATE SKIP LOCKED), a visibility timeout, ack/nack, and a dead-letter store — so concurrent consumers never double-claim a message. Because it is never auto-discovered, you must name it:

// queue.ts
import { createQueue, QueueProvider } from "@netscript/queue";

// provider: QueueProvider.Postgres is the ONLY way to select this backend.
const jobs = createQueue("jobs", {
  provider: QueueProvider.Postgres,
  connection: {
    postgres: {
      // url optional → falls back to Aspire's getPostgresUri().
      url: Deno.env.get("DATABASE_URL"),
      // tableName defaults to 'message_queue'.
      tableName: "message_queue",
    },
  },
});

await jobs.enqueue({ id: "job-1", kind: "reindex" });

Reach for Postgres when you already run it under Aspire and want the queue and your application data in one transactional store, rather than standing up a dedicated broker.

Step 4 — Use the in-memory backend in tests

For unit tests and examples, construct the MemoryQueueAdapter directly from the testing sub-path. It is volatile and in-process — nothing to provision, and it is never selected by auto-discovery, so production code paths stay untouched:

// jobs_test.ts
import { MemoryQueueAdapter } from "@netscript/queue/testing";

const queue = new MemoryQueueAdapter<{ id: string }>();

await queue.enqueue({ id: "job-1" });
await queue.listen(async (message) => {
  // assert on `message` here
});

Step 5 — Add concurrency with createParallelQueue

Any provider can be wrapped for concurrent processing with createParallelQueue. Pass a concurrency greater than 1 and a single listener processes that many messages at once — ideal for I/O-bound work (HTTP calls, DB queries). At concurrency: 1 (the default) it is identical to createQueue.

// queue.ts
import { createParallelQueue, QueueProvider } from "@netscript/queue";

// 8 messages processed concurrently on one listener, pinned to Redis.
const queue = createParallelQueue("notifications", {
  concurrency: 8,
  provider: QueueProvider.Redis,
});

await queue.listen(async (message) => {
  await deliver(message); // these run in parallel
});

For CPU-bound work, prefer Web Workers over queue concurrency — see Tune the worker runtime.

In-production pitfalls

See also

KV, queues & cron — the concept-level tour of queues, KV, and cron, and how Aspire wires the four queue backends.

Background jobs — when you want a managed worker job (persistence, retries, an HTTP trigger) instead of a raw queue.

Queue / KV / cron — the mechanics recipe: enqueue, consume, and schedule. This page pairs with it: choose the backend here, run it there.

For the full QueueProvider enum, the QueueConnectionOptions shape, createParallelQueue / ParallelQueueOptions, the MessageQueue interface, and the QueueError hierarchy, see

queue .