Skip to content

Quickstart

From nothing to one message pushed and consumed, with Docker, the JavaScript SDK, and plain curl.

Updated View as Markdown

Docker and five minutes. At the end you have a PostgreSQL, a broker, and one message that went in and came out. There is no schema to migrate and no extension to install: the broker embeds its DDL and its stored procedures, and applies them at boot.

Start a broker

  1. A network, so the broker can reach PostgreSQL by name.

    docker network create queen
  2. PostgreSQL. Any one the broker can reach works. The password has to match the next step.

    docker run -d --name queen-pg --network queen -e POSTGRES_PASSWORD=postgres postgres:16
  3. The broker. PG_USER and PG_DATABASE both default to postgres, so host, port and password are enough. It listens on PORT, default 6632. --restart on-failure:10 covers the seconds PostgreSQL spends initialising on its first boot: the broker refuses to start against a database it cannot reach, and Docker brings it back as soon as it can.

    docker run -d --name queen --restart on-failure:10 --network queen -p 6632:6632 -e PG_HOST=queen-pg -e PG_PORT=5432 -e PG_PASSWORD=postgres ghcr.io/queen-mq/queen:latest
  4. Check it. /health takes a connection from the pool and does a real round-trip, so a healthy answer means both processes work.

    curl -s http://localhost:6632/health
    {"status":"healthy","database":"connected","engine":"segments-rust","version":"1.2.1"}

    An empty answer on the first try means the broker is still waiting for PostgreSQL. Run it again a second later. If it is still empty after a few tries, docker logs queen says why.

The same binary serves its dashboard on the same port: open http://localhost:6632.

The bundled dashboard's overview: a header counting stored messages, queues, partitions, consumer groups, pending and completed, above a table giving throughput, pending delta, fill ratio, time lag and errors, each with its current value, a sentence of context and a sparkline.
Nothing was installed to get this. It is the binary you started above, on the port you already opened.

Push and consume

Nothing to create first: the queue and the partition are provisioned by the first push that names them. The only decision is the partition key, the thing whose events must stay in order.

npm i queen-mq
import { Queen } from 'queen-mq'

const client = new Queen('http://localhost:6632')
clients/client-js/test-v2/docs.jsjs
const res = await client
  .queue('orders')
  .partition('customer-42')
  .push([{ data: { orderId: 9137, amount: 99.5 } }])

.group('billing') names the cursor that remembers what this consumer has seen. .subscriptionMode('all') points a new cursor at the beginning instead of the tail, and .limit(1) stops the loop after one message.

clients/client-js/test-v2/docs.jsjs
await client
  .queue('orders')
  .group('billing')
  .subscriptionMode('all')
  .limit(1)
  .each()
  .consume(async (message) => {
    console.log(message.data)
  })

For the raw pop, without the managed loop:

clients/client-js/test-v2/docs.jsjs
const messages = await client
  .queue('orders')
  .batch(10)
  .wait(true)
  .pop()
curl -i -X POST http://localhost:6632/api/v1/push -H 'Content-Type: application/json' -d '{"items":[{"queue":"orders","partition":"customer-42","payload":{"hello":"world"}}]}'
[{"index":0,"message_id":"019842f1-...","transaction_id":"019842f1-...","queueName":"orders","status":"queued"}]
curl -i "http://localhost:6632/api/v1/pop/queue/orders?batch=1&wait=true&consumerGroup=demo&subscriptionMode=all"

That pop took a lease: the span belongs to this group until you acknowledge it or the lease expires. Acknowledge with the transactionId and partitionId from the message, plus the leaseId:

curl -i -X POST http://localhost:6632/api/v1/ack -H 'Content-Type: application/json' -d '{"transactionId":"019842f1-...","partitionId":"6b1c...","status":"completed","consumerGroup":"demo","leaseId":"019842f1-..."}'

An empty pop answers 204 with no body at all, so branch on the status code before parsing. The wire format is in the HTTP reference.

Two things worth doing before you move on

  • Push twice with the same transactionId. The second returns status: "duplicate" and the first message’s id, having written nothing.
  • Pop with a second consumer group. It gets the same message again, from its own cursor. Groups are fan-out.

Clean up

docker rm -f queen queen-pg && docker network rm queen
Navigation

Type to search…

↑↓ navigate↵ selectEsc close