Recommendations on App architecture
-
@SeRiusRod So to be clear, you’re planning on having a docker container that serves an SPA. This SPA is your scheduler interface. You’re not planning at this time to use either electron or cordova/capacitor. Correct so far?
The confusion comes from the fact that neither mqtt nor curl can run in a user’s browser. You probably could replace curl with fetch / axios, but you’ll probably have cors issues. Thus, you’ll need a backend node process. The node process will run on the server, and the quasar app is running on the client’s device.
Your structure would then be:
On the API (server): run mqtt, your scheduler / cron logic, and make curl requests.
On the quasar app (client): build an interface for scheduling, and POST or send via socket to api.
You’re correct that you can link the quasar app and api via socket.io, or just use regular polling GET requests. Note that these two “processes” aren’t running on the same machine unless your docker container is running on your desktop as opposed to a raspberry pi or whatever.
With this approach, your api is always running, and it will execute your scheduler no matter if a quasar page is open or not.
And to answer your fundamental question, I would probably not try to build these two projects together somehow. You could make your API also serve the html / js / css of the build quasar project, but nginx is better at that and it reduces complexity.
-
Thanks Beets, yes, now that’s correct.
@beets said in Recommendations on App architecture:
Thus, you’ll need a backend node process.
But before I go that approach, let me understand: If some code is added as a module or boot file, that code will not run in the server without the interface being active at the client?
BTW, i don’t need an API. If i can setup an in memory model, like a vuex store, populate with mqtt info and periodically dump to a json file that would do.
I have mqtt already running as a module.Wouldn’t another boot file work for the scheduler? (If it’s true that is code that runs in the server)
-
@SeRiusRod - Quasar’s boot files aren’t for server-side code or rather don’t run server-side. Boot files get ran at the boot up time of your app and are used to initialize/ setup things like i18n, API connectivity, etc. So, you need an API, either via REST, GraphQL or even Electron to be able to accomplish server-side tasks.
Scott
-
@SeRiusRod said in Recommendations on App architecture:
I have mqtt already running as a module.
Can you show how this code is running? I may have been mistaken and mqtt can run in the browser via websockets. However to accomplish your main goal that the scheduler always runs (and to make sure that two schedulers aren’t running at the same time), you really should architect this app as a node process running on the server and a thin quasar client to display / edit data.
-
@beets said in Recommendations on App architecture:
Can you show how this code is running?
Of course:
/* /src/boot/mqtt.js */ import Vue from 'vue' const bus = new Vue() const mqtt = require('mqtt') export default ({ app }) => { window.mqtt = mqtt const client = mqtt.connect('mqtt://<myserver>') window.mqtt.bus = bus window.mqttClient = client client.on('connect', () => { console.log('MQTT server connected successfully') // # means subscribe to all data client.subscribe('#', { qos: 0 }, (error) => { if (!error) { console.log('Subscription successful') } else { console.log('Subscription failed') } }) }) // Receive return data client.on('message', (topic, message) => { console.log(topic, ': \t\t\t\t', message.toString()) }) // Disconnect and reconnect client.on('reconnect', (error) => { console.log('Reconnecting...', error) }) // Link exception handling client.on('error', (error) => { console.log('Connection failed...', error) }) setInterval(function () { client.publish('tcc', 'Hello!') }, 5000) } export { mqtt }
This is a lightened sample. Not that I have much more. I was still struggling to create a project wide storage to dump the values received.
But you were completely right. There’s no code executed as long as there is no client connected.Now I’m building a nodejs service that receives mqtt info and stores in a json object that is committed to fs. Then it will schedule based on the events in that object, and also serve that json via sockets to the quasar frontend.
Any suggestions on how to make that global json storage object?
-
@SeRiusRod said in Recommendations on App architecture:
Any suggestions on how to make that global json storage object?
Vuex is the typical answer here. It would be a pretty simple store, with likely just one variable called
data
or whatever. First question is, have you set anything up with vuex (the src/store folder in quasar) ? -
@beets
That’s on the web side. But what about the server side? -
@SeRiusRod Sorry I thought you had that figured out from here:
stores in a json object that is committed to fs.
If you’re just looking to store an object, a global variable works fine, unless I’m still misunderstanding.
-
I’ve tried with global variables, but I could not make it work.
Now I’ve made a separate module for the storage management:// storage.js const fs = require('fs'); let settings = null; let devices = null; function getSettings() { try { settings = JSON.parse(fs.readFileSync('./settings.json', 'utf8')); } catch (error) { console.log('settings.json file not found.'); } if (!settings) settings = { A: true, B: false, }; console.log(settings); return settings; } function setSettings(newSettings) { fs.writeFileSync('./settings.json', JSON.stringify(newSettings)); } function getDevices() { try { devices = JSON.parse(fs.readFileSync('./devices.json', 'utf8')); } catch (error) { console.log('devices.json file not found.'); } if (!devices) devices = {}; console.log(devices); return devices; } function setDevices(newDevices) { fs.writeFileSync('./devices.json', JSON.stringify(newDevices)); } module.exports = { settings, devices, getSettings, setSettings, getDevices, setDevices, };
// server.js const config = require('./config'); var storage = require('./storage.js'); const mqtt = require('./mqtt.js') function main() { var settings = storage.getSettings(); console.log(`settings ${settings}`); //Works console.log(`storage.settings ${storage.settings}`); //Doesn't setInterval(function() { console.log('*Timer tick!*'); }, 5000); } main();
If you look on the two console outputs, the second one is null. And I don’t see why. So now I want to populate storage on mqtt.js, but isn’t available there.
-
@beets said in Recommendations on App architecture:
If you’re just looking to store an object, a global variable works fine, unless I’m still misunderstanding.
Yes, you’re right. I was having a background error that prevented my project to work as intended and that confused me.
-
I’ve made a bare NodeJs backend and setup a global variable for devices storage. That added a socket.io file that periodically sends a device.
On the Quasar frontend, I’ve added a boot file and setup one page for receiving the data. But I can’t find why it doesn’t receive anything, and the backend doesn’t inform about client’s connection. Perhaps you can give me a hand:Backend:
// socket.js const config = require('./config'); const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); console.log('socket: module loaded'); //This is shown app.get('/', (req, res) => { res.status(200).send('This is the backend server.'); //This works }); function sendDevice(id) { const device = devices.find((i) => i.id === id); io.emit('device', device); } function updateDevice(id, data) { var device = devices.find((i) => i.id === id); if (!device) devices.push(data); else device = data; pendingCommit = true; } http.listen(config.socketPort, () => { console.log('socket: websocket listening on *:' + config.socketPort); //This works }); // All of this doesn't seem to work io.on('connection', (socket) => { console.log(`socket: client connected with id: ${socket.id}`); socket.on('updateDevice', function(data) { if (!data.id) { console.error('socket: [ERROR] Received a device update request without a correct id.'); return; } updateDevice(data.id, data); }); setInterval(function() { sendDevice('1'); }, 5000); io.on('disconnect', () => { console.log(`socket: client disconnected with id: ${socket.id}`); }); }); module.exports = { sendDevice };
Frontend:
// boot/socket.js import io from 'socket.io-client' const socket = io.connect('http://localhost:3002') export default ({ Vue }) => { Vue.prototype.$socket = socket } export { socket }
<!-- pages/info.vue --> <template> <q-page padding class="window-height window-width row justify-center"> Selected: {{selected}}<br/> <!-- Shows the correct Id --> Name: {{device.display_name}} <!-- Shows default --> <q-knob readonly v-model="device.battery_level" show-value size="150px" :thickness="0.22" color="green" track-color="green-2" class="q-ma-md" > {{ device.battery_level }}% </q-knob> </q-page> </template> <script> export default { name: 'Info', data () { return { selected: '', settings: [], device: { id: '', display_name: 'Empty', battery_level: '-1' } } }, mounted () { this.selected = this.$q.localStorage.getItem('selectedItem') this.$socket.on('device', (data) => { this.device = [...this.device, data] }) } } </script>
-
Okey. I was getting a silent Cors error.