Node-RED

Node-RED HTTP para MQTT: exponha endpoints REST que publicam no seu broker

Sistemas legados, scripts de ERP e webhooks falam HTTP, enquanto seus dispositivos falam MQTT. Este tutorial monta uma ponte Node-RED HTTP para MQTT com validação, autenticação, endpoints de leitura e um padrão opcional de espera por confirmação.

Ponte Node-RED HTTP para MQTT transformando requisições REST de sistemas legados em comandos MQTT para dispositivos
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Uma ponte Node-RED HTTP para MQTT permite que qualquer sistema capaz de enviar uma requisição web dispare comandos em dispositivos MQTT, sem adicionar uma biblioteca MQTT a esse sistema. Neste tutorial você vai expor um endpoint REST no Node-RED que valida a requisição, publica um comando em um broker, opcionalmente espera a confirmação do dispositivo e devolve um código de status HTTP adequado.

Quando uma ponte HTTP para MQTT faz sentido

O MQTT é o protocolo certo entre os dispositivos e o broker, mas muito software em volta de uma planta ou de um prédio só conhece HTTP:

  • Um ERP ou MES que consegue chamar uma URL quando uma ordem de produção começa.
  • Um webhook de um produto SaaS, de um provedor de pagamentos ou de uma ferramenta de formulários.
  • Scripts shell e cron jobs que já usam curl.
  • Apps móveis ou dashboards que você não quer mantendo conexões MQTT de longa duração.
  • Um integrador terceirizado que só aceita um contrato REST.

Em vez de ensinar MQTT a cada um desses sistemas, você oferece a eles uma API HTTP pequena e bem definida. O Node-RED traduz cada requisição em um publish no topic certo. Os dispositivos continuam sendo clientes MQTT puros, e o broker continua sendo o único lugar por onde as mensagens passam.

Arquitetura do flow

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

Vamos construir três endpoints:

Método e caminho Função Resposta
POST /api/devices/:id/command Publica um comando e responde imediatamente 202 Accepted
POST /api/devices/:id/command?wait=1 Publica e espera a confirmação do dispositivo 200 OK ou 504 Gateway Timeout
GET /api/devices/:id/state Devolve o último estado conhecido via MQTT 200 OK ou 404 Not Found

Na demonstração usamos o servidor MQTT público gratuito em broker.connectmq.com com o prefixo connectmq/demo/h7r2pq. Use seu próprio prefixo aleatório e nunca envie comandos de controle reais pela área pública, já que qualquer pessoa pode ler e publicar ali; ela serve apenas para testes.

Passo 1: o endpoint HTTP e a validação

Adicione um node http in com método POST e URL /api/devices/:id/command. O Node-RED analisa automaticamente corpos JSON quando o cliente envia Content-Type: application/json e coloca os parâmetros de caminho em msg.req.params.

Ligue-o a um function node com duas saídas: a primeira vai para o MQTT, a segunda devolve erros diretamente a um node 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");   // defina no flow ou no ambiente

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 monte o topic MQTT diretamente a partir da entrada do usuário sem compará-la com uma lista de permissões. Uma requisição para o dispositivo # ou ../../other publicaria, de outra forma, em topics que você nunca pretendeu expor.

Passo 2: publique e responda

Ligue a primeira saída a dois lugares:

  1. Um node mqtt out com o campo de topic vazio, para que ele use msg.topic, conectado ao seu node de configuração do broker.
  2. Um function node que decide como responder a quem chamou via HTTP:
if (msg.wait) {
    // Estaciona a requisição até o dispositivo confirmar ou o temporizador disparar
    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;

Ligue este function node e a saída de erro do Passo 1 ao mesmo node http response. O node de resposta usa msg.statusCode e envia msg.payload como JSON.

A resposta assíncrona 202 é o padrão certo. Dispositivos MQTT podem estar dormindo, reconectando ou ocupados, e um cliente HTTP não deve ficar travado por isso. A variante wait=1 é útil para ferramentas interativas em que uma pessoa clica em um botão e espera saber se a bomba realmente ligou.

Passo 3: trate as confirmações do dispositivo

O dispositivo publica uma confirmação que repete o correlation ID, por exemplo em connectmq/demo/h7r2pq/pump1/ack:

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

Assine com um node mqtt in connectmq/demo/h7r2pq/+/ack (saída: JSON analisado) e associe a confirmação à requisição estacionada:

const ack = msg.payload;
const pending = flow.get("pending") || {};
const waiting = pending[ack.correlationId];
if (!waiting) return null;   // já expirou, ou é uma requisição 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;

Ligue-o ao mesmo node http response. Como a mensagem estacionada ainda carrega o objeto msg.res original, a resposta volta para o cliente HTTP certo. As requisições pendentes ficam na memória, o que não é problema, porque as conexões HTTP a que elas pertencem também desaparecem quando o Node-RED reinicia.

Se você usa MQTT 5, pode definir msg.responseTopic e msg.correlationData no comando de saída em vez de embutir o ID no JSON. O node mqtt out os repassa como propriedades MQTT 5, e os dispositivos que as suportam podem responder no topic solicitado. O guia de recursos do MQTT 5 explica request e response com mais detalhes.

Passo 4: um endpoint de leitura para o estado do dispositivo

Os dispositivos devem publicar seu estado como retained messages em connectmq/demo/h7r2pq/<id>/state. Assine connectmq/demo/h7r2pq/+/state e guarde cada um no contexto do flow:

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;

Depois adicione um node 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 as mensagens de estado são retidas, o Node-RED recebe o valor mais recente de cada dispositivo assim que se reconecta, então o endpoint de leitura é útil logo após uma reinicialização.

Testando com curl

Defina a chave de API como variável de ambiente do Node-RED (BRIDGE_API_KEY=change-me), faça o deploy e chame os endpoints na porta padrão 1880:

# Dispara e esquece
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"}'

# Espera a confirmação (retorna 504 após 5 s se nenhum dispositivo responder)
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}'

# Simula a confirmação do dispositivo (copie o correlationId do 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"}'

Protegendo uma ponte Node-RED HTTP para MQTT

Um endpoint HTTP que aciona bombas é uma superfície de ataque. Percorra esta lista antes de expô-lo além do localhost:

  1. Autentique toda requisição. A verificação de chave de API acima é o mínimo. Para autenticação básica em todos os endpoints HTTP-in, defina httpNodeAuth no settings.js com um hash bcrypt gerado por node-red admin hash-pw.
  2. Termine o TLS na frente do Node-RED com um proxy reverso como Nginx ou Caddy. Não exponha a porta 1880 diretamente na internet.
  3. Proteja o editor com adminAuth, ou rode o editor em outro caminho ou porta, apenas interno.
  4. Valide as entradas contra listas de permissões de dispositivos e ações e verifique os tipos dos valores.
  5. Limite a taxa no proxy e lembre-se de que o broker também impõe limites. O servidor público permite 10 mensagens por segundo por cliente, e os planos de produção têm suas próprias cotas.
  6. Use credenciais de broker privado sobre TLS na porta 8883 nos nodes mqtt.

Nosso artigo sobre como proteger o Node-RED aborda em profundidade o lado do editor e das configurações.

Migrando para um broker de produção

Substitua a configuração do broker de demonstração por um broker privado do ConnectMQ: porta 8883, TLS ativado e os valores YOUR_USERNAME, YOUR_PASSWORD e YOUR_CLIENT_ID gerados no portal. O ConnectMQ não oferece webhooks nem uma API HTTP de publicação própria, e é justamente por isso que esse padrão existe: o Node-RED é a camada voltada para HTTP, e o broker continua focado em MQTT. Rodar o broker como um broker MQTT gerenciado significa que o único servidor de que você cuida é a instância do Node-RED.

Rode sua ponte Node-RED no ConnectMQ

Crie um broker gratuitamente em app.connectmq.com no plano Serverless, sem cartão de crédito, e aponte sua ponte Node-RED para ele via TLS. Você pode acompanhar comandos e confirmações no explorador de topics ao vivo do portal enquanto testa seus endpoints com curl. Para os fundamentos dos nodes MQTT, veja o tutorial Node-RED MQTT e, para o sentido inverso, enviar alertas para pessoas, veja o guia de bot do Telegram no Node-RED.

Perguntas frequentes

O Node-RED pode receber webhooks e publicá-los no MQTT?

Sim. Um node http in aceita o webhook, um function node o valida e monta um topic, e um node mqtt out o publica. Devolva rapidamente uma resposta 2xx para que o remetente do webhook não tente de novo.

O endpoint HTTP deve esperar a resposta do dispositivo?

Devolva 202 Accepted por padrão e deixe os clientes consultarem o estado separadamente. Esperar uma confirmação é útil em uso interativo, mas sempre imponha um timeout curto.

Qual QoS devo usar para comandos vindos de HTTP?

QoS 1 é um bom padrão para comandos, combinado com ações idempotentes, como start e stop em vez de toggle, para que uma entrega duplicada não cause dano.

O ConnectMQ oferece uma API REST de publicação?

Não. O ConnectMQ é um broker MQTT com endpoints MQTT, TLS e WebSocket. Use o Node-RED ou um pequeno serviço próprio para traduzir requisições HTTP em publishes MQTT.

Artigos relacionados

Node-RED Dashboard 2.0 com MQTT: gauges, gráficos e controles

Um tutorial prático do Node-RED Dashboard 2.0: assine telemetria MQTT ao vivo, exiba-a em gauges e em um gráfico de linha e controle um dispositivo com um switch que publica comandos e reflete o estado real.

9 min de leitura

Cliente OPC UA no Node-RED para MQTT com topics ISA-95

Use o node-red-contrib-opcua para assinar variáveis OPC UA em um CLP ou servidor de simulação, mapear node IDs para topics MQTT no estilo ISA-95 e publicar os valores com qualidade e timestamps.

10 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.