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:
- 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. - 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:
- 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
httpNodeAuthnosettings.jscom um hash bcrypt gerado pornode-red admin hash-pw. - 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.
- Proteja o editor com
adminAuth, ou rode o editor em outro caminho ou porta, apenas interno. - Valide as entradas contra listas de permissões de dispositivos e ações e verifique os tipos dos valores.
- 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.
- 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.



