Service Bus
Lightweight, dependency-free fake of Azure Service Bus that speaks the documented Service Bus REST API over plain HTTP, so application code can drive queues, topics, subscriptions, rules and brokered messages with zero cost and zero side effects.
| Key | Value |
|---|---|
| Port | 4592 |
| Protocol | Azure Service Bus REST API (HTTP + Atom/XML management, brokered-message runtime) |
| Compatible client | @azure/service-bus (logical surface) / any HTTP client |
| Size | ~64 KB |
| Startup | < 100ms |
| State | In-memory, ephemeral, resettable |
Wire-transport note. The real
@azure/service-busSDK uses AMQP 1.0 for its data plane — a binary framing protocol that is intentionally out of scope for a tiny in-process fake. This fake instead implements Azure's documented HTTP/REST surface, which mirrors the same logical operations 1:1 (send / peek-lock receive / complete / abandon / renew-lock / dead-letter / defer / schedule, plus the full Atom-based management API). Agents and application code can therefore exercise every Service Bus concept without a broker. See the Supported vs unsupported table below.
Quick Start
Start the server:
import { ServicebusServer } from "./services/servicebus/src/server.js";
const server = new ServicebusServer(4592);
await server.start();
// ... use it ...
await server.stop();
Drive it over the Service Bus REST API with any HTTP client. The examples below use fetch.
Management (Atom/XML)
const base = "http://127.0.0.1:4592";
const ATOM = { "Content-Type": "application/atom+xml;type=entry;charset=utf-8" };
// Create a queue
await fetch(`${base}/orders`, {
method: "PUT",
headers: ATOM,
body: `<entry xmlns="http://www.w3.org/2005/Atom"><content type="application/xml">
<QueueDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect">
<MaxDeliveryCount>10</MaxDeliveryCount><LockDuration>PT30S</LockDuration>
</QueueDescription></content></entry>`,
});
// Create a topic + subscription
await fetch(`${base}/events`, { method: "PUT", headers: ATOM, body: `<entry xmlns="http://www.w3.org/2005/Atom"><content type="application/xml"><TopicDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"/></content></entry>` });
await fetch(`${base}/events/subscriptions/worker`, { method: "PUT", headers: ATOM, body: `<entry xmlns="http://www.w3.org/2005/Atom"><content type="application/xml"><SubscriptionDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"/></content></entry>` });
Send + receive (brokered messages)
// Send a message (BrokerProperties carries system metadata, custom headers map
// to application properties)
await fetch(`${base}/orders/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
BrokerProperties: JSON.stringify({ MessageId: "m1", Label: "new-order", CorrelationId: "c1" }),
priority: '"high"',
},
body: JSON.stringify({ id: 42 }),
});
// Peek-lock receive
const recv = await fetch(`${base}/orders/messages/head?timeout=30`, { method: "POST" });
const props = JSON.parse(recv.headers.get("brokerproperties"));
const body = await recv.text();
// Complete (delete) the locked message
await fetch(`${base}/orders/messages/${props.SequenceNumber}/${props.LockToken}`, { method: "DELETE" });
Using the real @azure/service-bus client
The SDK's ServiceBusAdministrationClient (management plane) issues exactly the
Atom/XML HTTP requests this fake implements. Point it at the local endpoint:
import { ServiceBusAdministrationClient } from "@azure/service-bus";
const admin = new ServiceBusAdministrationClient(
"Endpoint=sb://127.0.0.1:4592/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=parlellocaldevkey;UseDevelopmentEmulator=true"
);
await admin.createQueue("orders");
await admin.createTopic("events");
await admin.createSubscription("events", "worker");
The data-plane ServiceBusClient (sendMessages / receiveMessages) speaks
AMQP and is therefore not wire-compatible with this HTTP fake — use the REST
runtime endpoints above (or raw HTTP) to send/receive against the fake.
Implemented operations
Internal (parlel)
| Method | Path | Description |
|---|---|---|
| GET | /_parlel/health | Health check + entity counts |
| POST | /_parlel/reset | Reset all in-memory state |
| GET | /_parlel/dump | Dump queues / topics / subscriptions |
Management — Queues
| Method | Path | Operation |
|---|---|---|
| PUT | /{queue} | CreateQueue |
| GET | /{queue} | GetQueue |
| DELETE | /{queue} | DeleteQueue |
| GET | /$Resources/Queues | ListQueues |
Management — Topics
| Method | Path | Operation |
|---|---|---|
| PUT | /{topic} | CreateTopic |
| GET | /{topic} | GetTopic |
| DELETE | /{topic} | DeleteTopic |
| GET | /$Resources/Topics | ListTopics |
Management — Subscriptions
| Method | Path | Operation |
|---|---|---|
| PUT | /{topic}/subscriptions/{sub} | CreateSubscription |
| GET | /{topic}/subscriptions/{sub} | GetSubscription |
| DELETE | /{topic}/subscriptions/{sub} | DeleteSubscription |
| GET | /{topic}/subscriptions | ListSubscriptions |
Management — Rules
| Method | Path | Operation |
|---|---|---|
| PUT | /{topic}/subscriptions/{sub}/rules/{rule} | CreateRule (SqlFilter / CorrelationFilter / True / False, with optional SqlRuleAction) |
| GET | /{topic}/subscriptions/{sub}/rules/{rule} | GetRule |
| DELETE | /{topic}/subscriptions/{sub}/rules/{rule} | DeleteRule |
| GET | /{topic}/subscriptions/{sub}/rules | ListRules |
Every subscription is created with the default $Default TrueFilter rule.
Runtime — Messaging
| Method | Path | Operation |
|---|---|---|
| POST | /{queue|topic}/messages | Send (single) |
| POST | /{queue|topic}/messages + Content-Type: application/vnd.microsoft.servicebus.json | SendBatch |
| POST | /{entity}/messages/head?timeout=N | Receive (peek-lock) → 201 + LockToken |
| DELETE | /{entity}/messages/head?timeout=N | Receive-and-delete → 200 |
| DELETE | /{entity}/messages/{seq}/{lockToken} | Complete |
| PUT | /{entity}/messages/{seq}/{lockToken} | Abandon (unlock) — default disposition |
| PUT | /{entity}/messages/{seq}/{lockToken} + Disposition: deadletter | Dead-letter |
| PUT | /{entity}/messages/{seq}/{lockToken} + Disposition: defer | Defer |
| POST | /{entity}/messages/{seq}/{lockToken} | Renew lock |
| POST | /{entity}/messages/{sequenceNumber} | Receive deferred message by sequence number |
{entity} is one of:
{queue}— a queue{queue}/$DeadLetterQueue— the queue's dead-letter sub-queue{topic}/subscriptions/{sub}— a subscription{topic}/subscriptions/{sub}/$DeadLetterQueue— the subscription's dead-letter sub-queue
Message semantics
- Scheduled messages — set
BrokerProperties.ScheduledEnqueueTimeUtc; messages in the future are held until their time arrives. - Topic fan-out — a message published to a topic is copied to every subscription whose rules match.
- Filtering —
TrueFilter,FalseFilter,CorrelationFilter(CorrelationId / Label), and a useful subset ofSqlFilter(prop = 'value', numeric comparisons> < >= <= != <>, andAND/OR). - Dead-lettering — automatic when
deliveryCountexceeds the entity'sMaxDeliveryCounton abandon, or explicit via thedeadletterdisposition. - Lock expiry — peek-locks last 30s; expired locks are reclaimed and the message returns to the queue.
Surface coverage
This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.
Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.
| Feature | Supported |
|---|---|
| Queue / Topic / Subscription / Rule management (Atom) | ✅ |
| List queues / topics / subscriptions / rules | ✅ |
| Send (single + batch) | ✅ |
| Peek-lock receive | ✅ |
| Receive-and-delete | ✅ |
| Complete / Abandon / Renew-lock | ✅ |
| Dead-letter (auto on max-delivery + explicit) | ✅ |
Dead-letter queue addressing ($DeadLetterQueue) | ✅ |
| Defer + receive-by-sequence-number | ✅ |
| Scheduled (future-enqueue) messages | ✅ |
| Topic → subscription fan-out | ✅ |
| SQL / Correlation / True / False filters | ✅ (subset of SQL grammar) |
| BrokerProperties + custom application properties | ✅ |
AMQP 1.0 binary transport (ServiceBusClient data plane) | ⟳ Roadmap — intentionally — REST runtime endpoints used instead |
| Sessions (ordered, session-locked receive) | ⟳ Roadmap — stored on the entity but no session-locked receive |
Auto-forwarding (ForwardTo) | ✓ By design — accepted + persisted, not enforced |
| Duplicate detection | ✓ By design — accepted + persisted, not enforced |
| Shared Access Signature auth / RBAC | ✓ By design — all requests accepted (local fake) |
| Geo-disaster recovery, partitioning internals, metrics | ⟳ Roadmap |
Error codes / shapes
Errors are returned as Azure-style XML:
<Error><Code>404</Code><Detail>MessagingEntityNotFound: The messaging entity could not be found.</Detail></Error>
| HTTP status | Code | When |
|---|---|---|
| 400 | BadRequest | Malformed request / invalid JSON / sending to a subscription path |
| 404 | MessagingEntityNotFound | Queue/topic/subscription/rule does not exist |
| 409 | MessagingEntityAlreadyExists | Creating an entity that already exists |
| 410 | LockTokenNotFound | Lock token invalid or expired (complete/abandon/renew) |
| 500 | InternalServerError | Unexpected server error |
Successful responses use Azure's conventions:
- Create →
201with the entity's Atom<entry>. - Get →
200with the Atom<entry>. - Delete →
200(empty). - Send →
201. - Peek-lock receive →
201with the message body + aBrokerPropertiesresponse header containingLockToken,SequenceNumber,DeliveryCount, etc. - Receive-and-delete →
200with the message body. - Empty receive →
204(no message available).
Configuration — test.env
Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.
SERVICEBUS_NAMESPACE=parlel
SERVICEBUS_ENDPOINT=http://parlel-bridge:4592
SERVICEBUS_CONNECTION_STRING=Endpoint=sb://127.0.0.1:4592/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=parlellocaldevkey;UseDevelopmentEmulator=true
<!-- parlel:testenv:end -->