Node.js / TypeScript · recipe

Bridge OPC UA to MQTT in Node.js

node-opcua is a client-server stack, so the practical pattern is to subscribe over client-server and republish to MQTT. Carry the status code and SourceTimestamp into the payload, and derive the topic from the browse path.

Stacknode-opcua
Version2.181.1

The code

const { OPCUAClient, AttributeIds, TimestampsToReturn } = require("node-opcua");
const mqtt = require("mqtt");

const TOPIC = "plant/line1/machinetool1/state";

(async () => {
  const mq = mqtt.connect("mqtt://localhost:1883");
  await new Promise((r) => mq.on("connect", r));

  const client = OPCUAClient.create({ endpointMustExist: false });
  await client.connect("opc.tcp://opcua.umati.app:4840");
  const session = await client.createSession();

  const subscription = await session.createSubscription2({
    requestedPublishingInterval: 1000,
    requestedLifetimeCount: 100,
    requestedMaxKeepAliveCount: 10,
    publishingEnabled: true,
  });

  const item = await subscription.monitor(
    { nodeId: "i=2258", attributeId: AttributeIds.Value },
    { samplingInterval: 1000, queueSize: 10 },
    TimestampsToReturn.Both
  );

  item.on("changed", (dv) => {
    mq.publish(TOPIC, JSON.stringify({
      value: dv.value.value,
      status: dv.statusCode.name,
      sourceTimestamp: dv.sourceTimestamp,
    }));
  });

  // publish a connection-state topic so consumers can tell stale from unchanged
  subscription.on("terminated", () =>
    mq.publish("plant/line1/machinetool1/online", "false", { retain: true }));
  mq.publish("plant/line1/machinetool1/online", "true", { retain: true });
})();

What it prints

# in MQTT Explorer, plant/line1/machinetool1/state:
{"value":"2026-09-08T03:45:00.102Z","status":"Good","sourceTimestamp":"2026-09-08T03:45:00.102Z"}
# plant/line1/machinetool1/online: true  (retained)

Notes

  • This is a client-server subscription republished over MQTT, not OPC UA PubSub. Say so precisely in design documents: the two secure differently.
  • Publish a retained connection-state topic. Without it a consumer cannot distinguish a machine that is idle from a bridge that died.
  • Derive topics from the browse path rather than a hand-written map. Hand-written maps break at machine two.
  • Carry status and SourceTimestamp. A bare value cannot be audited later.
  • For real OPC UA PubSub, use a Part 14 implementation such as the .NET reference stack or open62541.

What breaks first

The bridge stops publishing and nothing downstream notices BadNoSubscription
Handle the subscription's terminated event, publish an offline state, and reconnect with backoff.

Last verified 7 September 2026 against OPC UA 1.05.07. OPC UA Connect is an educational resource and is not affiliated with the OPC Foundation.

Free download
OPC UA Troubleshooting & Integration Checklist
The field-tested checklist engineers use to diagnose certificates, ports and namespace problems fast, plus a Python (asyncua) client template.
Get the free checklist →
Free, no course, no login