Node-RED

Node-RED de HTTP a MQTT: expón endpoints REST que publican en tu broker

Los sistemas heredados, los scripts del ERP y los webhooks hablan HTTP, mientras que tus dispositivos hablan MQTT. Este tutorial construye un puente de HTTP a MQTT en Node-RED con validación, autenticación, endpoints de lectura y un patrón opcional de espera de confirmación.

Puente de HTTP a MQTT en Node-RED que convierte las peticiones REST de sistemas heredados en comandos MQTT para dispositivos
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

Un puente de HTTP a MQTT en Node-RED permite que cualquier sistema capaz de enviar una petición web dispare comandos en dispositivos MQTT, sin añadir una librería MQTT a ese sistema. En este tutorial expondrás un endpoint REST en Node-RED que valida la petición, publica un comando en un broker, espera opcionalmente a que el dispositivo lo confirme y devuelve un código de estado HTTP adecuado.

Cuándo tiene sentido un puente de HTTP a MQTT

MQTT es el protocolo adecuado entre los dispositivos y el broker, pero mucho del software que rodea a una planta o a un edificio solo conoce HTTP:

  • Un ERP o un MES que puede llamar a una URL cuando empieza una orden de trabajo.
  • Un webhook de un producto SaaS, un proveedor de pagos o una herramienta de formularios.
  • Scripts de shell y tareas cron que ya usan curl.
  • Apps móviles o dashboards en los que no quieres mantener conexiones MQTT de larga duración.
  • Un integrador externo que solo acepta un contrato REST.

En lugar de enseñar MQTT a cada uno de estos sistemas, les das una API HTTP pequeña y bien definida. Node-RED traduce cada petición en una publicación en el topic correcto. Los dispositivos siguen siendo clientes MQTT puros, y el broker sigue siendo el único lugar por el que fluyen los mensajes.

Arquitectura del flujo

HTTP client --POST /api/devices/pump1/command--> Node-RED
Node-RED --publish connectmq/demo/h7r2pq/pump1/cmd--> MQTT broker --> device
device --publish connectmq/demo/h7r2pq/pump1/ack--> MQTT broker --> Node-RED
Node-RED --HTTP 200 or 202 or 504--> HTTP client

Construiremos tres endpoints:

Método y ruta Propósito Respuesta
POST /api/devices/:id/command Publicar un comando y responder de inmediato 202 Accepted
POST /api/devices/:id/command?wait=1 Publicar y esperar la confirmación del dispositivo 200 OK o 504 Gateway Timeout
GET /api/devices/:id/state Devolver el último estado conocido desde MQTT 200 OK o 404 Not Found

Para la demo usamos el servidor MQTT público gratuito en broker.connectmq.com con el prefijo connectmq/demo/h7r2pq. Usa tu propio prefijo aleatorio y nunca envíes comandos de control reales a través del área pública, ya que cualquiera puede leer y publicar allí; es solo para pruebas y prototipos.

Paso 1: el endpoint HTTP y la validación

Añade un nodo http in con el método POST y la URL /api/devices/:id/command. Node-RED parsea automáticamente los cuerpos JSON cuando el cliente envía Content-Type: application/json, y coloca los parámetros de ruta en msg.req.params.

Conéctalo a un nodo function con dos salidas: la primera va a MQTT y la segunda devuelve los errores directamente a un nodo http response.

const PREFIX = "connectmq/demo/h7r2pq";
const ALLOWED_DEVICES = ["pump1", "pump2", "valve3"];
const ALLOWED_ACTIONS = ["start", "stop", "setpoint"];
const API_KEY = env.get("BRIDGE_API_KEY");   // definida en el flujo o en el entorno

function fail(code, message) {
    msg.statusCode = code;
    msg.payload = { error: message };
    return [null, msg];
}

if (!API_KEY || msg.req.headers["x-api-key"] !== API_KEY) {
    return fail(401, "invalid or missing API key");
}

const id = msg.req.params.id;
if (!ALLOWED_DEVICES.includes(id)) return fail(404, "unknown device " + id);

const body = msg.payload;
if (typeof body !== "object" || body === null) return fail(400, "JSON body required");
if (!ALLOWED_ACTIONS.includes(body.action)) return fail(400, "action must be one of " + ALLOWED_ACTIONS.join(", "));
if (body.action === "setpoint" && typeof body.value !== "number") {
    return fail(400, "setpoint requires a numeric value");
}

const correlationId = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
const command = {
    action: body.action,
    value: body.value,
    correlationId,
    issuedAt: new Date().toISOString(),
    source: "http"
};

msg.deviceId = id;
msg.correlationId = correlationId;
msg.wait = msg.req.query.wait === "1";
msg.topic = `${PREFIX}/${id}/cmd`;
msg.payload = JSON.stringify(command);
msg.qos = 1;
msg.retain = false;
return [msg, null];

Nunca construyas el topic MQTT directamente a partir de la entrada del usuario sin comprobarlo contra una lista de permitidos. De lo contrario, una petición para el dispositivo # o ../../other publicaría en topics que nunca quisiste exponer.

Paso 2: publicar y responder

Conecta la primera salida a dos lugares:

  1. Un nodo mqtt out con el campo topic vacío, para que use msg.topic, conectado a tu nodo de configuración del broker.
  2. Un nodo function que decide cómo responder al llamante HTTP:
if (msg.wait) {
    // Aparcar la petición hasta que el dispositivo confirme o salte el temporizador
    const pending = flow.get("pending") || {};
    pending[msg.correlationId] = msg;
    flow.set("pending", pending);

    setTimeout(() => {
        const p = flow.get("pending") || {};
        const waiting = p[msg.correlationId];
        if (waiting) {
            delete p[msg.correlationId];
            flow.set("pending", p);
            waiting.statusCode = 504;
            waiting.payload = { error: "device did not acknowledge in time", correlationId: msg.correlationId };
            node.send(waiting);
        }
    }, 5000);
    return null;
}

msg.statusCode = 202;
msg.payload = { status: "accepted", correlationId: msg.correlationId, topic: msg.topic };
return msg;

Conecta este nodo function y la salida de errores del paso 1 al mismo nodo http response. El nodo de respuesta usa msg.statusCode y envía msg.payload como JSON.

La respuesta asíncrona 202 es el valor por defecto correcto. Los dispositivos MQTT pueden estar dormidos, reconectándose u ocupados, y un cliente HTTP no debería quedarse colgado por eso. La variante wait=1 es útil en herramientas interactivas en las que una persona pulsa un botón y espera saber si la bomba arrancó realmente.

Paso 3: gestionar las confirmaciones del dispositivo

El dispositivo publica una confirmación que repite el correlation ID, por ejemplo en connectmq/demo/h7r2pq/pump1/ack:

{ "correlationId": "lrx2k9ab12cd", "result": "ok", "state": "running" }

Suscríbete con un nodo mqtt in a connectmq/demo/h7r2pq/+/ack (salida: JSON parseado) y emparéjala con la petición aparcada:

const ack = msg.payload;
const pending = flow.get("pending") || {};
const waiting = pending[ack.correlationId];
if (!waiting) return null;   // ya expiró, o era una petición 202

delete pending[ack.correlationId];
flow.set("pending", pending);

waiting.statusCode = ack.result === "ok" ? 200 : 409;
waiting.payload = { status: ack.result, state: ack.state, correlationId: ack.correlationId };
return waiting;

Conéctalo al mismo nodo http response. Como el mensaje aparcado sigue llevando el objeto msg.res original, la respuesta vuelve al llamante HTTP correcto. Las peticiones pendientes viven en memoria, lo cual está bien, porque las conexiones HTTP a las que pertenecen también desaparecen cuando Node-RED se reinicia.

Si usas MQTT 5, puedes establecer msg.responseTopic y msg.correlationData en el comando saliente en lugar de incrustar el ID en el JSON. El nodo mqtt out los pasa como propiedades de MQTT 5, y los dispositivos que las admiten pueden responder en el topic solicitado. La guía de funciones de MQTT 5 explica request/response con más detalle.

Paso 4: un endpoint de lectura para el estado del dispositivo

Los dispositivos deberían publicar su estado como retained messages en connectmq/demo/h7r2pq/<id>/state. Suscríbete a connectmq/demo/h7r2pq/+/state y guarda cada uno en el contexto del flujo:

const id = msg.topic.split("/")[3];
const states = flow.get("states") || {};
states[id] = { state: msg.payload, receivedAt: new Date().toISOString() };
flow.set("states", states);
return null;

Después añade un nodo http in para GET /api/devices/:id/state:

const states = flow.get("states") || {};
const s = states[msg.req.params.id];
if (!s) {
    msg.statusCode = 404;
    msg.payload = { error: "no state received for this device" };
} else {
    msg.statusCode = 200;
    msg.payload = s;
}
return msg;

Como los mensajes de estado son retenidos, Node-RED recibe el último valor de cada dispositivo en cuanto se reconecta, así que el endpoint de lectura es útil inmediatamente después de un reinicio.

Pruebas con curl

Define la API key como variable de entorno para Node-RED (BRIDGE_API_KEY=change-me), haz deploy y llama a los endpoints en el puerto por defecto 1880:

# Enviar y olvidar
curl -i -X POST http://localhost:1880/api/devices/pump1/command \
  -H "Content-Type: application/json" -H "X-API-Key: change-me" \
  -d '{"action":"start"}'

# Esperar la confirmación (devuelve 504 tras 5 s si ningún dispositivo responde)
curl -i -X POST "http://localhost:1880/api/devices/pump1/command?wait=1" \
  -H "Content-Type: application/json" -H "X-API-Key: change-me" \
  -d '{"action":"setpoint","value":42.5}'

# Simular la confirmación del dispositivo (copia el correlationId del topic cmd)
mosquitto_sub -h broker.connectmq.com -p 1883 -i dev-h7r2pq -v -t "connectmq/demo/h7r2pq/pump1/cmd"
mosquitto_pub -h broker.connectmq.com -p 1883 -i ack-h7r2pq -t "connectmq/demo/h7r2pq/pump1/ack" \
  -m '{"correlationId":"PASTE_ID","result":"ok","state":"running"}'

Proteger un puente de HTTP a MQTT en Node-RED

Un endpoint HTTP que mueve bombas es una superficie de ataque. Repasa esta lista antes de exponerlo más allá de localhost:

  1. Autentica cada petición. La comprobación de API key anterior es el mínimo. Para autenticación básica en todos los endpoints HTTP-in, configura httpNodeAuth en settings.js con un hash bcrypt generado con node-red admin hash-pw.
  2. Termina TLS delante de Node-RED con un proxy inverso como Nginx o Caddy. No expongas el puerto 1880 directamente a internet.
  3. Protege el editor con adminAuth, o sirve el editor en una ruta o un puerto distinto y solo interno.
  4. Valida las entradas con listas de permitidos para dispositivos y acciones, y comprueba los tipos de los valores.
  5. Limita la frecuencia en el proxy, y recuerda que el broker también aplica límites. El servidor público permite 10 mensajes por segundo por cliente, y los planes de producción tienen sus propias cuotas.
  6. Usa credenciales privadas del broker sobre TLS en el puerto 8883 para los nodos mqtt.

Nuestro artículo sobre cómo proteger Node-RED trata a fondo el editor y la configuración.

Pasar a un broker de producción

Sustituye la configuración del broker de demo por un broker privado de ConnectMQ: puerto 8883, TLS activado y los valores YOUR_USERNAME, YOUR_PASSWORD y YOUR_CLIENT_ID generados en el portal. ConnectMQ no ofrece webhooks ni una API HTTP de publicación propia, y precisamente por eso existe este patrón: Node-RED es la capa que da la cara a HTTP, y el broker se centra en MQTT. Ejecutar el broker como un broker MQTT gestionado significa que el único servidor que tienes que cuidar es la instancia de Node-RED.

Ejecuta tu puente de Node-RED sobre ConnectMQ

Crea un broker gratis en app.connectmq.com con el plan Serverless, sin tarjeta de crédito, y apunta tu puente de Node-RED hacia él por TLS. Puedes ver los comandos y las confirmaciones en el explorador de topics en vivo del portal mientras pruebas tus endpoints con curl. Para los fundamentos de los nodos MQTT, consulta el tutorial de Node-RED con MQTT, y para la dirección inversa, enviar alertas a personas, consulta la guía del bot de Telegram con Node-RED.

Preguntas frecuentes

¿Puede Node-RED recibir webhooks y publicarlos en MQTT?

Sí. Un nodo http in acepta el webhook, un nodo function lo valida y construye un topic, y un nodo mqtt out lo publica. Devuelve rápidamente una respuesta 2xx para que el emisor del webhook no reintente.

¿Debe el endpoint HTTP esperar la respuesta del dispositivo?

Devuelve 202 Accepted por defecto y deja que los clientes consulten el estado por separado. Esperar una confirmación es útil en el uso interactivo, pero aplica siempre un tiempo de espera corto.

¿Qué QoS debo usar para los comandos que llegan por HTTP?

QoS 1 es un buen valor por defecto para los comandos, combinado con acciones idempotentes como start y stop en lugar de toggle, para que una entrega duplicada no cause daños.

¿Ofrece ConnectMQ una API REST de publicación?

No. ConnectMQ es un broker MQTT con endpoints MQTT, TLS y WebSocket. Usa Node-RED o un pequeño servicio propio para traducir las peticiones HTTP en publicaciones MQTT.

Artículos relacionados

Cliente OPC UA en Node-RED hacia MQTT con topics ISA-95

Usa node-red-contrib-opcua para suscribirte a variables OPC UA de un PLC o de un servidor de simulación, asignar los node IDs a topics MQTT de estilo ISA-95 y publicar los valores con calidad y marcas de tiempo.

10 min de lectura

Node-RED Dashboard 2.0 con MQTT: indicadores, gráficos y controles

Un tutorial práctico de Node-RED Dashboard 2.0: suscríbete a telemetría MQTT en directo, muéstrala en indicadores y en un gráfico de líneas, y controla un dispositivo con un interruptor que publica comandos y refleja el estado real.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.