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.
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.
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
In this step we create our Meteor project
$ meteor create quickblox-meteor
$ cd quickblox-meteor
$ meteor npm install
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
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
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: '' }
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); //
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 }) } })
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 });
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>
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}) } }); }
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) }
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; }
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.