Skip to content

Service Bus

This recipe covers integrating Azure Service Bus with Azure Functions Node.js v4 — consuming queue and topic/subscription messages, handling dead-lettering, and publishing messages with the output binding.

Architecture

flowchart TD
    PROD[Producers] --> SBQ[(Service Bus Queue/Topic)]
    SBQ --> TRIG[Service Bus Trigger]
    TRIG --> FA[Function App]
    FA --> DLQ[(Dead-Letter Queue)]
    FA --> OUT[Service Bus Output]

Prerequisites

Provide the connection in app settings. A connection-string setting or an identity-based connection is supported. Identity-based connections use a setting prefix with __fullyQualifiedNamespace:

az functionapp config appsettings set \
  --name $APP_NAME \
  --resource-group $RG \
  --settings "ServiceBusConnection__fullyQualifiedNamespace=$NAMESPACE.servicebus.windows.net"
CLI element Explanation
Command(s) az functionapp config appsettings set
Key flags --name, --resource-group, --settings
Variables $APP_NAME, $RG, $NAMESPACE
Expected result Azure CLI returns the updated app settings as JSON; confirm the setting is present before continuing.

When using an identity-based connection, grant the function app's managed identity the Azure Service Bus Data Receiver (and Data Sender for output) role on the namespace.

Queue Trigger

Message settlement is automatic: completing the handler settles the message, and throwing abandons it. After maxDeliveryCount the message is dead-lettered.

const { app, output } = require("@azure/functions");

const ordersOutput = output.serviceBusQueue({
  queueName: "orders",
  connection: "ServiceBusConnection"
});

app.serviceBusQueue("processOrder", {
  queueName: "orders",
  connection: "ServiceBusConnection",
  handler: (message, context) => {
    context.log("Message ID:", context.triggerMetadata.messageId);
    context.log("Delivery count:", context.triggerMetadata.deliveryCount);
    context.log("Payload:", message);
    // Throwing here abandons the message; after maxDeliveryCount it is
    // moved to the dead-letter subqueue automatically.
  }
});

Topic/Subscription Trigger

app.serviceBusTopic("processEvent", {
  topicName: "events",
  subscriptionName: "billing",
  connection: "ServiceBusConnection",
  handler: (message, context) => {
    context.log("Subscription message:", message);
  }
});

Output Binding: Publish Messages

app.http("enqueue", {
  methods: ["POST"],
  authLevel: "function",
  extraOutputs: [ordersOutput],
  handler: async (request, context) => {
    const body = await request.json();
    context.extraOutputs.set(ordersOutput, body);
    return { status: 202, jsonBody: { status: "enqueued" } };
  }
});

Host Configuration

{
  "version": "2.0",
  "extensions": {
    "serviceBus": {
      "maxConcurrentCalls": 16,
      "prefetchCount": 0,
      "maxAutoLockRenewalDuration": "00:05:00"
    }
  }
}
Setting Description
maxConcurrentCalls Maximum concurrent message handlers per instance
prefetchCount Number of messages the client prefetches to reduce latency
maxAutoLockRenewalDuration How long the runtime keeps renewing the message lock during processing

Sessions and ordering

Enable isSessionsEnabled: true on the trigger to process session-enabled queues/subscriptions, which guarantees ordered, single-consumer processing per session ID.

See Also

Sources