Skip to content

Quickstart: send your first message

Run Narad on your machine, then create a topic and produce, consume and ack one message with curl, in about five minutes.

Before you start: curl, plus one of Docker, Homebrew, or Go 1.26 or later with git and make.

Step 1: start Narad

Each option below runs one Narad node on your machine, serving its API on port 7942 with authentication off.

docker run --rm -p 127.0.0.1:7942:7942 \
  -v narad-data:/var/lib/narad \
  -e NARAD_SECURITY_ENABLED=false \
  -e NARAD_CLUSTER_ADDR=127.0.0.1:7943 \
  ghcr.io/debanganthakuria/narad:v3.2.2

The container logs one JSON line per event. Narad is serving once a line contains "msg":"http listening".

  • -p 127.0.0.1:7942:7942 publishes the API on your machine only, which matters because authentication is off.
  • -v narad-data:/var/lib/narad keeps your topics and messages in a Docker volume named narad-data.
  • NARAD_CLUSTER_ADDR gives the node's internal cluster port an address it can advertise. Without it the container exits at once with local bind address is not advertisable.
brew install debanganthakuria/narad/narad
narad server start --dev

Homebrew builds Narad from source, so the install takes a minute or more. --dev binds the node to 127.0.0.1:7942, turns authentication off and keeps data in ~/.narad/data. It prints this banner, then log lines:

Output
  narad  dev mode: auth OFF, bound to loopback only

  Try it from another terminal:

    narad topic add demo
    narad sub demo --peek        # terminal B: watch messages flow
    narad pub demo '{"hello":"narad"}' --count 100 --rate 20

  Or plain curl:

    curl -X POST 'http://127.0.0.1:7942/v1/topics' -H 'Content-Type: application/json' -d '{"name":"demo"}'

The narad commands in the banner are the CLI's own demo, described in Narad CLI.

git clone --branch v3.2.2 --depth 1 https://github.com/DebanganThakuria/narad
cd narad
make build
./bin/narad server start --dev

make build writes the binary to bin/narad. Build from the release tag rather than with go install: @latest resolves to an old v1 release, because the module path has no /v3 suffix. --dev binds the node to 127.0.0.1:7942, turns authentication off and keeps data in ~/.narad/data. It prints this banner, then log lines:

Output
  narad  dev mode: auth OFF, bound to loopback only

  Try it from another terminal:

    narad topic add demo
    narad sub demo --peek        # terminal B: watch messages flow
    narad pub demo '{"hello":"narad"}' --count 100 --rate 20

  Or plain curl:

    curl -X POST 'http://127.0.0.1:7942/v1/topics' -H 'Content-Type: application/json' -d '{"name":"demo"}'

The narad commands in the banner are the CLI's own demo, described in Narad CLI.

Leave Narad running. Open a second terminal and check that the node is ready:

export NARAD=http://127.0.0.1:7942
curl -i "$NARAD/readyz"
Response
HTTP/1.1 200 OK
Content-Length: 19
Content-Type: application/json
Date: Mon, 28 Sep 2026 19:31:56 GMT

{"status":"ready"}
  • $NARAD is the base URL of the node. Every command on this page uses it.

A 503 means the node is still starting; try again after a second or two. Authentication is off on this node, so the commands on this page send no credentials. A real cluster, such as one from Deploy on Kubernetes, needs a username and password on every request: see Connect and authenticate.

Step 2: create a topic

curl -i -X POST "$NARAD/v1/topics" \
  -H "Content-Type: application/json" \
  -d '{"name": "orders", "visibility_timeout_ms": 300000}'
Response
HTTP/1.1 201 Created
Content-Length: 209
Content-Type: application/json
Date: Mon, 28 Sep 2026 19:31:57 GMT

{"name":"orders","id":"74b7c37f76f5ae56","partitions":3,
"retention_ms":604800000,"visibility_timeout_ms":300000,
"max_in_flight_per_partition":1024,"max_acked_ahead_per_partition":1024,
"created_at":1790623916}

The topic orders now exists, with the server's defaults for everything you did not set: 3 partitions, and messages kept for 7 days (retention_ms). visibility_timeout_ms gives a consumer 5 minutes to ack each message, instead of the default 30 seconds, so you have time to copy a value in step 5.

Every POST on this page sends Content-Type: application/json. Without it, Narad answers 415 Unsupported Media Type.

Step 3: produce a message

curl -i -X POST "$NARAD/v1/topics/orders/produce?key=customer-42" \
  -H "Content-Type: application/json" \
  -d '{"order_id": "ord_123", "amount": 4999}'
Response
HTTP/1.1 202 Accepted
Date: Mon, 28 Sep 2026 19:31:57 GMT
Content-Length: 0

The request body is the message: any bytes, up to 1 MiB. The key customer-42 keeps messages about one customer on the same partition. Produce messages covers keys, binary payloads and every status code.

What a 202 means

A 202 Accepted means the node that answered has written your message to its write-ahead log and fsynced it. From then on a crash, restart or power loss of any node does not lose it. Consumers do not see it yet: the node hands it to the partition's owner in the background, usually within milliseconds. A produce that times out may or may not have been accepted, so retrying it can create a duplicate.

The full promise is in the delivery contract.

Step 4: consume it

curl -i "$NARAD/v1/topics/orders/consume?wait=10s"
Response
HTTP/1.1 200 OK
Content-Length: 180
Content-Type: application/json
Date: Mon, 28 Sep 2026 19:31:57 GMT

{"topic":"orders","partition":1,"offset":0,"key":"customer-42",
"payload":{"order_id": "ord_123", "amount": 4999},
"timestamp":1790623917,"receipt_handle":"1:0:3742135424316369939"}
  • payload is the JSON you produced, byte for byte.
  • wait=10s makes the request wait up to 10 seconds for a message, then answer 204 No Content if none arrived.
  • The message is now leased to you: no other consumer gets it until you ack it or the topic's visibility timeout (5 minutes here) runs out.
  • receipt_handle names this one delivery. You send it back to ack the message; treat it as an opaque string. See receipt handle.

If this answers 204 No Content at once, run it again after a few seconds: a node that started moments ago may not have placed the topic's partitions yet. Consume and acknowledge messages covers leases, extends and nacks in full.

Step 5: ack it

The receipt handle travels from the consume answer to the ack Your terminal talks to the one local Narad node from step 1, listening on 127.0.0.1:7942. Step 3: POST /v1/topics/orders/produce answers 202 Accepted, and the message is stored at offset 0 of orders/1. Step 4: GET .../consume?wait=10s answers 200 with a receipt_handle, 1:0:3742135424316369939, which names this one delivery. Step 5: you send that handle back in POST .../ack?receipt_handle=..., which answers 204 No Content. An ack after the lease ran out gets 410. Your terminal Narad 127.0.0.1:7942 0 orders/1 3 POST /v1/topics/orders/produce 202 Accepted 4 GET .../consume?wait=10s 200 + receipt_handle 1:0:37… 5 POST .../ack?receipt_handle=... 204 No Content an ack after the lease ran out gets 410 The receipt handle travels from the consume answer to the ack Your terminal talks to the one local Narad node from step 1, listening on 127.0.0.1:7942. Step 3: POST /v1/topics/orders/produce answers 202 Accepted, and the message is stored at offset 0 of orders/1. Step 4: GET .../consume?wait=10s answers 200 with a receipt_handle, 1:0:3742135424316369939, which names this one delivery. Step 5: you send that handle back in POST .../ack?receipt_handle=..., which answers 204 No Content. An ack after the lease ran out gets 410. Your terminal 1:0:37… 3 4 5 Narad 127.0.0.1:7942 0 orders/1 3 POST /v1/topics/orders/produce 202 Accepted 4 GET .../consume?wait=10s 200 + receipt_handle 5 POST .../ack?receipt_handle=... 204 No Content an ack after the lease ran out gets 410
Copy the receipt_handle from the consume answer into the ack. It names this one delivery, so it settles the message only while your lease lasts.

Put the receipt_handle from your step 4 response in a variable. Yours differs from the one shown here, so replace the value:

HANDLE='1:0:3742135424316369939'

Then ack the message:

curl -i -X POST \
  "$NARAD/v1/topics/orders/ack?receipt_handle=$HANDLE" \
  -H "Content-Type: application/json"
Response
HTTP/1.1 204 No Content
Date: Mon, 28 Sep 2026 19:31:57 GMT

The message is settled. Run the step 4 command again: it waits 10 seconds and answers 204 No Content, because orders has nothing left to deliver. An ack that comes after the lease ran out answers 410 Gone instead, and the message goes to the next consume.

Delivery is at least once

Narad delivers a message until a consumer acks it, as long as the topic's retention still holds it, and it can deliver the same message more than once: after a lease expires, after a nack, or after a broker crash. Make every handler idempotent, for example by keying its work on an ID in the payload.

Every case is in the delivery contract.

To stop Narad, press Ctrl+C in the first terminal. Your topic and message stay on disk for the next start: in the narad-data volume with Docker, or in ~/.narad/data with --dev.

Next steps

  • Core concepts: the ideas behind topics, keys, leases and acks.
  • Produce messages: keys, binary payloads and what each status code means.
  • Go SDK: produce and consume from a Go service.