One pattern I refused to copy from old tutorials: NUI fetch() with hardcoded resource names. Nightmare to maintain.
Here's a tiny bus I use across all my resources:
-- ui_bus.lua (client)
local listeners = {}
RegisterNUICallback('bus', function(data, cb)
local handlers = listeners[data.channel] or {}
for _, h in ipairs(handlers) do h(data.payload) end
cb({ ok = true })
end)
function busOn(channel, fn)
listeners[channel] = listeners[channel] or {}
table.insert(listeners[channel], fn)
end
function busSend(channel, payload)
SendNUIMessage({ channel = channel, payload = payload })
end
// ui-bus.js (NUI side)
const listeners = {};
window.addEventListener('message', (e) => {
const { channel, payload } = e.data;
(listeners[channel] || []).forEach(fn => fn(payload));
});
export function on(channel, fn) {
(listeners[channel] = listeners[channel] || []).push(fn);
}
export async function send(channel, payload) {
return fetch(`https://${GetParentResourceName?.() || 'guardianfx-ui'}/bus`, {
method: 'POST',
body: JSON.stringify({ channel, payload }),
}).then(r => r.json());
}
You can now bus.on('inventory:updated', ...) from JS and busSend('inventory:updated', items) from Lua, and none of your resources need to know each other's names.
Survives restarts, survives renames, survives moving features between resources.