Vue, Vuex and sails.io.js (websockets)

Derek Colley
4 min readMar 18, 2022

--

vuetify.js -> websockets -> sails.js

My goto stack for rapid prototyping is:

Typically, you might use axios.js for REST connection to backend/api.

Here is an example of how to bind sails.io.js into Vuex in order to use websocket connection to a sails.js api.

Project structure

mkdir projcd proj
sails create sails-api
cd sails-api
sails lift # runs on port 1337
<new terminal>cd proj
vue create vuetify-frontend
cd vuetify-frontend
vue add router
vue add store
vue add vuetify
npm install --save socket.io-client@2
npm install --save sails.io.js
npm run serve # runs on port 8080

Dependencies

Sails.io requires socket.io-client v2

npm install –-save socket.io-client@2

Vue & Vuex

vue.config.js

module.exports = {
transpileDependencies: [‘vuetify’],
devServer: {
disableHostCheck: true,
port: 8080,
host: ‘0.0.0.0’,
// use this to proxy axios calls to /api
proxy: {
‘^/api’: {
target: ‘http://localhost:1337/',
changeOrigin: true
},
// use this to proxy ws calls to sails.io
‘^/socket.io’: {
target: ‘http://localhost:1337/',
ws: true,
// changeOrigin: true
},
}
}

src/store/plugins/sailsIOPlugin.js

// kudos: https://lukashermann.dev/writing/socketio-with-vue-and-vuex/
// websocketStorePlugin.js
export default function createSailsIOPlugin (io) {
return store => {
store.$io = io
io.socket.on(‘message’, payload => store.dispatch(‘receiveMessage’, payload))
}
}

src/store/sails.io.js

// src/store/sails.io.js
// var url = ‘http://localhost:1337'
var url = `${location.protocol}//${location.hostname}:${location.port}`
var io = require(‘sails.io.js’)( require(‘socket.io-client’) )
io.sails.url = url
export default io

src/store/index.js

import createSailsIOPlugin from ‘./plugins/sailsIOPlugin’
import io from ‘./sails.io’
const sailsIOPlugin = createSailsIOPlugin(io)
import message from ‘./modules/message’
const store = new Vuex.Store({
namespaced: true,
plugins: [
sailsIOPlugin,
],
modules: {
message,
},
actions: {
init({dispatch}) {
this.$io.socket.get(‘/api/message/subscribe’, function(res, jwres) {
console.debug(res, jwres)
})
console.debug(‘index.js: actions.init()’)
dispatch(“message/getList”, {}, {rootScope: true})
},
receiveMessage ({dispatch}, payload) {
console.debug(“receiveMessage”, payload)
dispatch(`${payload.model}/${payload.action}`, payload.data, {rootScope: true})
},
},
})
export default store

src/store/modules/message.js

const message = {
namespaced: true,
state: {
loading: false,
list: [],
model: {},
offset: 0,
limit: 100,
error: null,
},
getters: {
messages(state) {
return state.list
},
},
mutations: {
SET_LIST(state, list) { state.list = list },
SET_MODEL(state, model) { state.model = model },
SET_OFFSET(state, offset) { console.debug('SET_OFFSET', offset); state.offset = offset},
SET_LIMIT(state, limit) { console.debug('SET_LIMIT', limit); state.limit = limit },
SET_LOADING(state, loading) { state.loading = loading },
CREATE_MESSAGE(state, message) {
console.debug('CREATE_MESSAGE', message)
let idx = state.list.findIndex(function(i){ i.id == message.id })
console.debug("idx", idx, message)
if(idx > -1) {
state.list[idx] = message
} else {
console.debug('pushing...')
state.list.push(message)
}
}
},
},
actions: {
async getList({state, commit}, payload={}) {
await commit("SET_LOADING", true)
// console.debug("getList", payload, state)
// let {offset, limit} = payload
if(payload.offset) { await commit("SET_OFFSET", payload.offset) }
if(payload.limit) { await commit("SET_LIMIT", payload.limit) }
let params = {
user_id: 2,
offset: state.offset,
limit: state.limit,
}
this.$io.socket.get('/api/message', params, async function(res, jwres) {
if(res.error) console.error('socket ERROR', res, jwres)
await commit("SET_LIST", res.data)
await commit("SET_LOADING", false)
})
},
// send message to server
async post({rootState, commit}, payload) {
let params = {
user_id: rootState.auth.user.id,
text: payload.text,
ref_id: payload.ref_id
}
this.$io.socket.post('/api/message', params, async function(res, jwres){
if(res.error) console.error('websocket error', res, jwres)
await commit("CREATE_MESSAGE", res.data)
})
},
// receive message from server model Message.create()
async create({commit}, payload) {
await commit("CREATE_MESSAGE", payload)
},
async clearError({commit}) {
await commit("SET_ERROR", null)
},
},
}
export default message

Sails.js

config/routes.js

module.exports.routes = {
// in addition to your other routers...
'GET /api/message/subscribe': 'message/subscribe',
'GET /api/message/unsubscribe': 'message/unsubscribe',
'GET /api/message': 'message/get',
'GET /api/message/:id': 'message/get',
'POST /api/message': 'message/create',
}

controllers/message/subscribe.js

module.exports = {
fn: async function(inputs, exits, env) {
var {req, res} = env
if (!req.isSocket) {
return res.badRequest();
}
var roomName = id ? `message_${id}` : 'messages'
var socketId = sails.sockets.getId(req)
sails.sockets.join(socketId, roomName, function(err) {
if (err) { return res.serverError(err) }
sails.log.debug("new socket", socketId)
return res.json({
message: 'Subscribed to '+roomName+'!'
})
})
}
}

Some pointers

Subscribe to ‘message’

Have a look at `vue-frontend/src/store/index.js: actions.init()`. This is the link to the subscribe controller action in Sails.js.

You call this in your App.vue:

created() {
this.$store.dispatch('init')
}

Once the subscription is done, the sails API can broadcast the ‘message’ object. In the console you should see:

Browser Console: receiveMessage

Refs and Kudos

I found many useful resources on the net. Here is a link to the article that helped me specifically with Vuex. Special thanks to Lukas!

https://lukashermann.dev/writing/socketio-with-vue-and-vuex/

If you liked this article please comment, clap and follow me for more!

You can also reach me at http://twitter.com/_derekc_

--

--