==

Q-Consultation for every industry

Securely hold virtual meetings and video conferences

Learn More>

Want to learn more about our products and services?

Speak to us now

How to Create a Real-Time Messaging System with Meteor and QuickBlox

Hamza Mousa
23 Jul 2021
Real time messaging with QuickBlox and Meteor

Meteor is a JavaScript development framework for creating real-time progressive web apps and cross-platform hybrid applications.

Unlike other JavaScript frameworks and platforms, Meteor offers different JavaScript UI frameworks for the client-side. It works seamlessly for React, React Native, Angular, Vue, and Blaze frameworks.

Meteor uses MonogoDB; a NoSQL document database with its own version that works from the browser Minimongo with full data synchronization support.

We have written a snap tutorial on how to use Meteor and Cordova with QuickBlox to create iOS and Android apps, you may check it out here.

Why did we choose Meteor?

  1. Large ecosystem using NPM and Atmosphere;
  2. Meteor is agnostic, it supports React, Vue, Blaze, and Angular;
  3. Mobile-ready through Apache Cordova;
  4. Scalable;

In this tutorial, we will explain how to create a messaging app with meteor and QuickBlox messaging service which offers several messaging functionalities.

QuickBlox offers several tools that work with meteor with include a JavaScript SDK, React Native SDK, and a REST-API.

We will use QuickBlox JavaScript SDK which includes all QuickBlox features.

Let’s start

1. Get Meteor

If you are on Linux or macOS, open your terminal and write:

curl https://install.meteor.com/ | sh

If you are using Windows you can install Meteor as an NPM package:

npm install -g meteor

2. Setup a meteor Project

In this step we create our Meteor project

$ meteor create quickblox-meteor
$ cd quickblox-meteor
$ meteor npm install

3. Install QB

Now, we will install QuickBlox JavaScript SDK which we will use to connect, login, send and receive messages.

$ meteor npm install quickblox
$ meteor add hamzamu:settings

4. Install the required libraries

I am using Materialize CSS framework which works seamlessly with Blaze, also will install moment.js (Date and Time manipulation library) and loDash (Functional JavaScript library).

$ meteor npm install materialize-css
$ meteor npm install moment
$ meteor npm install lodash

5. QuickBlox Setup

We need to setup QuickBlox CREDENTIALS that we will use to connect to our QuickBlox developer account, login, create dialogs, and retrieve messages.

Note that is not the best practice with Meteor on the client-side, You may use .env file or a setting file on the server-side, but we used this method as a demonstration which can also be used for Mobile development.

JavaScript

CREDENTIALS = {
    appId : '',
    authKey : '',
    authSecret : '',
    // Public Room
    roomjID : '',
    // Group Dialog
    groupRId: ''
}

6. Initiate the app

JavaScript
import {
  Template
} from 'meteor/templating';
import {
  ReactiveVar
} from 'meteor/reactive-var';
import QB from 'quickblox'
import moment from 'moment'
import './auth.js'
import '../node_modules/materialize-css/dist/js/materialize.js'
import '../node_modules/materialize-css/dist/css/materialize.css'
import './main.html';


/**
 * QuickBlox Init
 */
QB.init(CREDENTIALS.appId, CREDENTIALS.authKey, CREDENTIALS.authSecret);
//

7. QB login

Here, we will create QuickBlox login template and logic.

html
<!--  -->
<template name="login">
  <!-- login Windows -->
  <div class="valign-wrapper vh card-panel teal lighten-2">
    <div class="card ">
      <form class="card-content col s12" id="login">
        <div class="row">
          <div class="s12">
            <h4>Login</h4>
          </div>
          <div class="input-field col s12">
            <input id="username" type="text" name="username" class="validate">
            <label for="username">login</label>
          </div>
          <div class="input-field col s12">
            <input id="password" type="password" name="password" class="validate">
            <label for="password">Password</label>
          </div>
          <div class="input-field col s12">
            <button class="btn waves-effect waves-light" type="submit" name="action">Login
            </button>
          </div>
        </div>
      </form>
    </div>
  </div>
</template>
JavaScript



function login(params) {

  QB.createSession(params, function (err, result) {
    // callback function
    if (err) {
      console.log('Session error: Can`t login to QB')
      alert('Session error: Can`t login to QB')
    } else {
      console.log('Session Created User login: ', params.login, {
        result
      })
      App.setSetting({
        login: true
      })
      getUserId(params.login,params.password)
    }
  });
}


function getUserId(login,password){
  var searchParams = {
    login: login
  };

  QB.users.get(searchParams, function (error, user) {
    if(!error){
      console.log('Getting User Id: ',user)
      App.setSetting({currentUser: user})

      connect({userId:user.id, password:password})
    }
  });
}

/** Login */
Template.login.events({
  'submit #login'(e) {
    e.preventDefault();
    var username = $('input[name = "username"]').val()
    var password = $('input[name = "password"]').val()
    login({
      login: username,
      password: password
    })
  }
})

8. DB setup (for dialog and users)

Meteor offers a client-side Mongo collection “MiniMongo” which we will use to save, sort, search and filter our messages and users. The collections are temporary but we can use some packages to make them persistent.

JavaScript
// Messages local collection
Messages = new Mongo.Collection('Messages', {
  connection: null
});

// User Collection
Users = new Mongo.Collection('Users', {
  connection: null
});

9. Messaging UI

As we are getting messages into our a local MongoDB collection, we will need to display it in our view. Using helpers and Blaze template.

JavaScript

/**
 * Message Helpers
 */
Template.messages.helpers({
  // Messages Helper
  messages() {
    return Messages.find({}, {
      sort: {
        date_sent: 1
      },
      limit: 40
    })
  },
  // Getting user name
  getUser(id, key) {
    var user = Users.findOne({
      id: id
    })
    if (!user) {
      return
    }
    return user[key];
  }
})



/**
 * Moment Helper
 */

Template.registerHelper('moFromNow', function (date) {
  return moment(date).fromNow();
})

/**
 *  Get Self.messages
*/

Template.registerHelper('mine', function (userId) {
  if (userId == App.getSetting('userId')) {
    return true
  }
})
html
<template name="mainView">
  {{>messages}}
</template>
<template name="messages">
  <div id="messages" class="">
    {{#each messages}}
    <div class="{{#if mine this.sender_id}}mine{{else}}yours{{/if}} messages">
      <div class="message">
        <b>{{getUser this.sender_id 'login'}} - {{moFromNow created_at withoutSuffix}}</b> <br />
        {{message}} {{body}}
      </div>
    </div>
    {{/each}}
  </div>
  <div id="newMessage">
    <div class="row">
      {{>newMessage}}
    </div>
  </div>
</template>
<template name="newMessage">
  <form name="newMessage" id="newMessage">
    <div class="input-field col s8">
      <input type="text" id="newMessageBody" class="" placeholder="Write something" />
    </div>
    <div class="input-field col s2">
      <a href="#" class="btn" id="setNewMessage">Submit</a>
    </div>
  </form>
</template>

10. Get dialog messages

In the next code snippet, we will fetch the last dialog messages.

JavaScript

// ============================
// Get Latest Messages
// ============================
function getMessages(dialogId) {
  //Getting Messages
  //
  console.log('Getting the Dialog Messages...')
  var params = {
    chat_dialog_id: dialogId,
    // sort_desc: 'date_sent',
    sort_desc: 'date_sent',
    limit: 10,
    skip: 0
  };
  QB.chat.message.list(params, function (error, messages) {
    if (!error) {
      if (messages.limit > 0) {
        _.each(messages.items, (msg) => {
          if (!Messages.findOne({
              _id: msg._id
            })) {
            Messages.insert(msg)
          }
        })
      }
    } else {
      console.log({error})
    }
  });
}

11. Subscribe to new messages

JavaScript

// setUser Helper

function setUser(userId) {
  var user = Users.findOne({
    id: userId
  });
  if (!user) {
    console.log('user does not exist')
    console.log('Getting User`s data')
    //
    var searchParams = {
      filter: {
        field: 'id',
        param: 'in',
        value: [userId]
      }
    };
    // Get user
    QB.users.listUsers(searchParams, function (error, result) {
      if (!error) {
        var user = result.items[0];
        Users.insert(user.user)
      } else {
        console.error('Could not get the user details [QB.users.listUsers]')
      }
    });
  }
}

// MessageListener
QB.chat.onMessageListener = onMessage;

function onMessage(userId, message) {
  Messages.insert(message)
  setUser(userId)


}

Wrapping up

Here we wrap up our code with a few more tweaks that will come in handy when developing a real-life application.

Note that is a demonstration with a full client-side capability, You can make your own server-side version with a similar approach and make use of Meteor amazing tools.

JavaScript

import {
  Template
} from 'meteor/templating';
import {
  ReactiveVar
} from 'meteor/reactive-var';
import QB from 'quickblox'
import moment from 'moment'
import './auth.js'
import '../node_modules/materialize-css/dist/js/materialize.js'
import '../node_modules/materialize-css/dist/css/materialize.css'
import './main.html';



// Create App Configuration Collection
AppConfigs = new Mongo.Collection('AppConfigs', {
  connection: null
});
// create a local persistence observer
var appconfigsObs = new PersistentMinimongo2(AppConfigs, 'appConfigs');
// Messages Collection
Messages = new Mongo.Collection('Messages', {
  connection: null
});
// create a local persistence observer
var messages = new PersistentMinimongo2(Messages, 'appConfigs');
//

Users = new Mongo.Collection('Users', {
  connection: null
});
// create a local persistence observer
var users = new PersistentMinimongo2(Users, 'users');

/**
 * QuickBlox Init
 */
QB.init(CREDENTIALS.appId, CREDENTIALS.authKey, CREDENTIALS.authSecret);
//




/**
 * Login
 * @param {*} params
 *  params.login
 *  params.password
 */
function login(params) {

  QB.createSession(params, function (err, result) {
    // callback function
    if (err) {
      console.log('Session error: Can`t login to QB')
      App.setSetting({
        login: false,
        message: 'loginError: createSession is not successful'
      })
      alert('Session error: Can`t login to QB')
    } else {
      console.log('Session Created User login: ', params.login, {
        result
      })
      App.setSetting({
        login: true
      })
      getUserId(params.login,params.password)
    }
  });
}


function getUserId(login,password){
  var searchParams = {
    login: login
  };

  QB.users.get(searchParams, function (error, user) {
    if(!error){

      App.setSetting({currentUser: user})

      connect({userId:user.id, password:password})
    }
  });
}

/** Login */
Template.login.events({
  'submit #login'(e) {
    e.preventDefault();
    var username = $('input[ name = "username"]').val()
    var password = $('input[name = "password"]').val()
    login({
      login: username,
      password: password
    })
  }
})



function connect(userCredentials) {
  QB.chat.connect(userCredentials, function (error, contactList) {
    if (!error) {
      App.setSetting({connected:true})

        var roomId = App.getSetting('roomjID')
      // Join Specific Room
      console.log('Joining Dialog*')
      QB.chat.muc.join(roomId, function (error, result) {
        if (error) {
          console.error('Could not join the dialog', roomId)
          return
        }

        var dialogId = result.dialogId
        App.setSetting({
          dialogId: dialogId
        })
        // Getting Messages List
        getMessages(dialogId)
      });
    } else {
      console.error('QB Connection Error')
      alert('Could not connect to the QB')
    }
  });
}

// ============================
// Send a Message
// ============================
function sendMessage(roomId, message) {
  message.id = QB.chat.send(roomId, message);

}
// ============================
// Get Latest Messages
// ============================
function getMessages(dialogId) {
  //Getting Messages
  //

  var params = {
    chat_dialog_id: dialogId,
    // sort_desc: 'date_sent',
    sort_desc: 'date_sent',
    limit: 10,
    skip: 0
  };
  QB.chat.message.list(params, function (error, messages) {
    if (!error) {

      if (messages.limit > 0) {
        _.each(messages.items, (msg) => {
          if (!Messages.findOne({
              _id: msg._id
            })) {

            Messages.insert(msg)
          }
        })
      }
    } else {
      console.log({error})
    }
  });
}




/**
 * ================================
 * Subscribe To Messages
 * ===============================
 * *** */
QB.chat.onMessageListener = onMessage;

function onMessage(userId, message) {

  Messages.insert(message)
  setUser(userId)


}
/**
 * ================================
 * Set Users
 * ===============================
 * *** */
function setUser(userId) {
  var user = Users.findOne({
    id: userId
  });
  if (!user) {
    console.log('user does not exist')
    console.log('Getting User`s data')
    //
    var searchParams = {
      filter: {
        field: 'id',
        param: 'in',
        value: [userId]
      }
    };
    // Get user
    QB.users.listUsers(searchParams, function (error, result) {
      if (!error) {
        var user = result.items[0];
        Users.insert(user.user)
      } else {
        console.error('Could not get the user details')
      }
    });
  }
}


/**
 * Message Helpers
 */
Template.messages.helpers({
  messages() {
    return Messages.find({}, {
      sort: {
        date_sent: 1
      },
      limit: 40
    })
  },
  getUser(id, key) {
    var user = Users.findOne({
      id: id
    })
    if (!user) {
      return
    }
    return user[key];
  }
})
/**
 * Sending a new Message
 */
Template.newMessage.events({
  'click #setNewMessage': (e) => {
    e.preventDefault();
    var message = $('#newMessageBody').val()
    if (!message) {
      alert('Please write a message')
    }
    var roomId = App.getSetting('roomjID')
    var dialogId = App.getSetting('dialogId')
    var message = {
      type: "groupchat",
      body: message,
      extension: {
        save_to_history: 1,
        dialog_id: roomId
      },
      markable: 1
    };
    sendMessage(roomId, message)
    $('#newMessageBody').val(" ")


  }
})

/**
 * Moment Helper
 */

Template.registerHelper('moFromNow', function (date) {
  return moment(date).fromNow();
})

/**
 *  Get Self.messages
*/

Template.registerHelper('mine', function (userId) {
  if (userId == App.getSetting('userId')) {
    return true
  }
})
html
<head>
  <title>QuickBlox Meteor Messages Application</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> -->
</head>

<body>
  {{#unless getSetting 'connected'}}
  {{>login}}
  {{else}}
  {{>mainView}}
  {{/unless}}
</body>
<template name="mainView">
  {{>messages}}
</template>
<template name="messages">
  <div id="messages" class="">
    {{#each messages}}
    <div class="{{#if mine this.sender_id}}mine{{else}}yours{{/if}} messages">
      <div class="message">
        <b>{{getUser this.sender_id 'login'}} - {{moFromNow created_at withoutSuffix}}</b> <br />
        {{message}} {{body}}
      </div>
    </div>
    {{/each}}
  </div>
  <div id="newMessage">
    <div class="row">
      {{>newMessage}}
    </div>
  </div>
</template>
<template name="newMessage">
  <form name="newMessage" id="newMessage">
    <div class="input-field col s8">
      <input type="text" id="newMessageBody" class="" placeholder="Write something" />
    </div>
    <div class="input-field col s2">
      <a href="#" class="btn" id="setNewMessage">Submit</a>
    </div>
  </form>
</template>
<!--  -->
<template name="login">
  <!-- login Windows -->
  <div class="valign-wrapper vh card-panel teal lighten-2">
    <div class="card ">
      <form class="card-content col s12" id="login">
        <div class="row">
          <div class="s12">
            <h4>Login</h4>
          </div>
          <div class="input-field col s12">
            <input id="username" type="text" name="username" class="validate">
            <label for="username">login</label>
          </div>
          <div class="input-field col s12">
            <input id="password" type="password" name="password" class="validate">
            <label for="password">Password</label>
          </div>
          <div class="input-field col s12">
            <button class="btn waves-effect waves-light" type="submit" name="action">Login
            </button>
          </div>
        </div>
      </form>
    </div>
  </div>
</template>
css
body {
  margin: 0px;
  padding: 0px;
  font-family: sans-serif;
  overflow: hidden;

}

.vh{
  height: 100vh;
}

#messages{
  height: 90vh;
  width: 90vw;
  overflow-x: auto !important;
  overflow-y: auto !important;

}

#newMessage{
  height: 5vh;
}


.messages {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
}

.message {
  border-radius: 20px;
  padding: 8px 15px;
  margin-top: 5px;
  margin-bottom: 5px;
  display: inline-block;
}

.yours {
  align-items: flex-start;
}

.yours .message {
  margin-right: 25%;
  background-color: #eee;
  position: relative;
}

.yours .message.last:before {
  content: "";
  position: absolute;
  z-index: 0;
  bottom: 0;
  left: -7px;
  height: 20px;
  width: 20px;
  background: #eee;
  border-bottom-right-radius: 15px;
}
.yours .message.last:after {
  content: "";
  position: absolute;
  z-index: 1;
  bottom: 0;
  left: -10px;
  width: 10px;
  height: 20px;
  background: white;
  border-bottom-right-radius: 10px;
}

.mine {
  align-items: flex-end;
}

.mine .message {
  color: white;
  margin-left: 25%;
  background: linear-gradient(to bottom, #00D0EA 0%, #0085D1 100%);
  background-attachment: fixed;
  position: relative;
}

.mine .message.last:before {
  content: "";
  position: absolute;
  z-index: 0;
  bottom: 0;
  right: -8px;
  height: 20px;
  width: 20px;
  background: linear-gradient(to bottom, #00D0EA 0%, #0085D1 100%);
  background-attachment: fixed;
  border-bottom-left-radius: 15px;
}

.mine .message.last:after {
  content: "";
  position: absolute;
  z-index: 1;
  bottom: 0;
  right: -10px;
  width: 10px;
  height: 20px;
  background: white;
  border-bottom-left-radius: 10px;
}

Conclusion

In this article, we explored how can we integrate QuickBlox with Meteor, send messages, receive a new message, subscribe to new messages, and connect to channels (dialogs). This simple app can be extended to take the full potential of Meteor real-time reactivity and multi-platform support.

The main core advantage of using Meteor is: the developer can have access to two different package systems (NPM and Atmosphere) as well as choosing the right framework to do the job.

Using Meteor gives developers different options and a wide-range of libraries and frameworks to use. Developers can create mobile apps, Interactive web apps, desktop apps, server apps, and WebRTC-based video communication applications.

Have Questions? Need Support?

Join the QuickBlox Developer Discord Community, where you can share ideas, learn about our software, & get support.

Join QuickBlox Discord

Leave a Comment

Your email address will not be published. Required fields are marked *

Read More

Ready to get started?

QUICKBLOX
QuickBlox post-box