Matt Chaffe

FeathersJS Channel Subscriptions


FeathersJS Channels are a great way to handle real-time data. However, sometimes you might only want certain data on a certain page. For example, you may have a chatroom app which has multiple rooms and you only want to provide data for a room that a user is viewing.

On the server

To be able to handle channels on the fly, I am going to configure a custom service which will handle joining and leaving channels.

class RoomMembershipService {
async create (data, params) {
if (params.connection) {`room/${data.roomId}`).join(params.connection)
return data;
async remove (id, params) {
if (params.connection) {`room/${id}`).leave(params.connection)
return { id };
setup (app) { = app;

In the above snippet custom service class we use the create method to join the channel and the remove method to leave the channel.


Now to configure the service to emit data to the channels, within the *-service.js file, we can configure a custom publish. This will emit data only to the channels which exist and will filter out the current user. So the user that created the message within the room, won’t get an .on('created')event when they create a new message, but all other subscribed users will get this.

const service = app.service('messages')
// Publish events
service.publish((data, context) => {
// Filter the channels to only authenticated
.filter(connection => connection.user._id.toString() !== data.userId.toString())
view raw service.js hosted with ❤ by GitHub

Awesome! Now we are only emitting data to those that want it. Now let’s take a look at setting up the client.

The client

For our discussion here, I am just going to show a brief example of where you would emit the subscribe/unsubscribe events.

tag: 'messages',
view: `<div>
<div class="wrapper" >
{{# for(message of messages) }}
TO: {{ users.usersById[].email }}
MSG: {{ }}
FROM: {{ users.usersById[message.from].email }}
{{/ for}}
<form on:submit="sendMessage(scope.event)">
<input name="to" placeholder="To" value:bind="to">
<input name="msg" placeholder="Message.." value:bind="msg">
<button type="submit">Create</button>
ViewModel: {
msg: 'string',
to: 'string',
messages: 'any',
users: 'any',
roomId: 'string',
get messagesAndUsers () {
return Promise.all([
user: {
get: () => Session.current.user
messagesPromise: {
default: () => Messages.getList({ $or: [{ from: Session.current.user._id }, { to: Session.current.user._id }] })
usersPromise: {
default: () => User.getList()
sendMessage (event) {
const toUser = this.users.usersByEmail[]
if (toUser) {
new Messages({ name: this.msg, to: toUser._id, roomId: this.roomId })
.then(() => {
this.msg = '' = ''
connectedCallback () {
// Listen to the session prop so we can load messages
// once we have an user
this.listenTo('user', (e, newVal) => {
// Load both users and messages and assign to VM
this.messagesAndUsers.then(([messages, users]) => {
this.messages = messages
this.users = users
feathersClient.service('room-membership').create({ roomId: this.roomId })
// Teardown handler
return () => {
// Remove all listeners
view raw message.component.js hosted with ❤ by GitHub

The above example is of a CanJS component. We use connectedCallback which is a lifecycle hook called after the component’s element is inserted into the document. This can also return a function which will be used when the component is torn down / removed from the document.

Within the connectedCallback we can emit the subscribe event, and in the teardown, we can emit the unsubscribe. When a user navigates to this component it will automatically subscribe the current user to the room, so all messages that are created with this roomId will be sent to this room channel.

To see how this affects your app, if you open up the network tab in devtools, and select the ws sub-tab you will see in the frames section all the data transferred to and from your server.

If the server was configured to publish to the authenticated channel which would be all logged in users. Every user would see created events for messages and rooms which they are not viewing. This means that the server is sending data to those that aren’t using it or require it, so we can prevent this by only sending data to those that are subscribed to specific channel.

Thanks for reading!

Published 12 February 2019

Dad, Husband, Software Developer