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 build a desktop chat application using QuickBlox and Electron

Artem Koltunov
30 May 2024
Logos of QuickBlox, React, and Electron

Summary: Read our extensive tutorial to learn how to build a desktop chat application using QuickBlox and Electron.

Table of Contents


Welcome to our developer tutorial on building a powerful desktop chat application using QuickBlox and Electron! In this tutorial, we will walk you through the process of creating a robust and feature-rich chat application that runs seamlessly on desktop platforms. By combining the capabilities of QuickBlox, a versatile cloud communication platform, with the flexibility and ease of use of Electron, a framework for building cross-platform desktop applications, we’ll empower you to create a dynamic chat experience for your users. From setting up your development environment to implementing essential chat features such as real-time messaging and file sharing, we’ll cover every step of the process in detail.

Geared towards novice developers, this article simplifies the complexities of cross-platform desktop application development using advanced technologies. We will provide simple instructions to ensure accessibility for those unfamiliar with these tools. Our goal is to help you to develop a cross-platform application capable of running on MacOS, Linux, and Windows operating systems, providing standard chat functionality with messaging and file exchange similar to applications like WhatsApp and Facebook Messenger. Throughout this tutorial, we’ll provide clear explanations, code samples, and practical examples to help you understand the concepts and techniques involved in building a desktop chat application with QuickBlox and Electron. By the end, you’ll have a fully functional chat application that you can customize and extend to meet the specific needs of your project or application.


Before we get started, let’s introduce you to some of the key technologies you’ll be using in this project.

  • Electron
  • To achieve cross-platform compatibility, we’ll utilize Electron. This open-source framework developed by GitHub enables developers to build cross-platform desktop applications using web technologies such as HTML, CSS, and JavaScript.

  • QuickBlox JS SDK
  • For chat functionality implementation, we’ll use QuickBlox JS SDK, which grants access to cloud services for chats and AI functionality. QuickBlox JS SDK is a powerful tool for developers seeking to integrate real-time communication features into web applications. It offers a comprehensive suite of APIs and functionalities to facilitate the development of chat applications, video calling solutions, and other real-time communication tools. With QuickBlox JS SDK, developers can easily implement features such as one-on-one messaging, group chats, file sharing, push notifications, and more.

  • QuickBlox React UI Kit
  • Additionally, we will employ React Chat UI Kit to create a user-friendly chat interface. Our UI Kit provides a collection of pre-built user interface components specifically designed for developers building chat applications using React.js and QuickBlox. It offers a range of customizable components, including chat bubbles, message input fields, user avatars, and more, to streamline the development process and enhance the visual appeal of chat interfaces.
    The QuickBlox React UI Kit will enable us to easily integrate pre-built chat into our project.

  • React
  • In this project we will also be implementing the renderer process using React mechanisms to construct the user interface. React is an open-source JavaScript library developed by Facebook for building user interfaces, particularly for web applications. It provides developers with a declarative and component-based approach to building UIs, making it easier to manage and update complex user interfaces

Let’s Get Started!

Setting Up the Project

Let’s create a project directory and init a node project inside it. To do so, run the following commands:

 mkdir electron_chat
 cd electron_chat
 npm init

Here are the npm commands for installing the dependencies, categorized as follows:

Installing React:

 npm install react react-dom @babel/core @babel/preset-env @babel/preset-react babel-loader

Installing Electron:

 npm install electron 

Installing Webpack:

 npm install webpack webpack-cli css-loader sass-loader style-loader

Installing QuickBlox SDK and React UI Kit:

 npm install quickblox quickblox-react-ui-kit

Executing these commands will install all the required dependencies to develop an application utilizing React, Electron, Webpack, and Quickblox SDK with React UI Kit.

Upon completion, your package.json file will resemble the following:

   "name": "electron_chat",
   "version": "1.0.0",
   "description": "",
   "main": "main.js",
   "scripts": {
 	"start": "electron .",
 	"watch": "webpack --config webpack.common.js --watch"
   "author": "QB Developer",
   "license": "ISC",
   "devDependencies": {
 	"electron": "^23.1.1"
   "dependencies": {
 	"@babel/core": "^7.21.0",
 	"@babel/preset-env": "^7.20.2",
 	"@babel/preset-react": "^7.18.6",
 	"babel-loader": "^9.1.2",
 	"css-loader": "^6.7.3",
 	"quickblox": "^2.16.1",
 	"quickblox-react-ui-kit": "^0.2.8",
 	"react": "^18.2.0",
 	"react-dom": "^18.2.0",
 	"sass": "^1.58.3",
 	"sass-loader": "^13.2.0",
 	"style-loader": "^3.3.1",
 	"webpack": "^5.75.0",
 	"webpack-cli": "^5.0.1"

Pay attention to the line “main”: “main.js”; we have changed it from the original “main”: “index.js”. This modification was necessary to align with the standard React application project structure, where index.js serves as the main entry point and App.js as the main component. Considering that our Electron application comprises both renderer and main processes, main.js functions as the primary entry point for the Electron application.

Note: the line watch": "webpack --config webpack.common.js --watch will be described in the section “Testing and Debugging” below.

Creating the Basic App Structure

Let’s start by creating the basic structure of the application.

Create Basic Structure:

  • Next to the package.json file, create a main.js file.
  • This file will contain the code for the Electron main process, written in Node.js. It can perform operations like accessing the operating system, file creation or reading, and working with a local database.

Set Up Directories:

  • Create a src directory.
  • Inside the src directory, create a js directory.

Add Renderer Process Files:

  • Inside the js directory, create index.js and App.js files.

Set Up Webpack:

  • Create a webpack.common.js file next to main.js to configure Webpack for the renderer process.

Review File Contents:

  • Open the main.js file and examine its structure to ensure it meets your requirements.

Content of main.js is:

 // main processing use node js
 const {app, BrowserWindow } = require('electron');
 const path = require("path");

 function createWindow(){
 	// renderer processing
 	const win = new BrowserWindow({
     	backgroundColor: "white",
     	webPreferences: {
        	 nodeIntegration: false,
         	contextIsolation: true,
         	worldSafeExecuteJavaScript: true,
         	preload: path.join(__dirname, 'preload.js'),
 	win.webContents.openDevTools({ mode: 'detach' });
 app.on('window-all-closed', ()=>{
 	if (process.platform !== 'darwin'){
 app.on('activate', ()=>{
 	if (BrowserWindow.getAllWindows().length === 0){

The main.js file serves as the entry point for the Electron main process, which orchestrates the application’s core functionality. Let’s review the purpose of each function:

  1. createWindow(): This function creates a new browser window for the renderer process. It configures the window’s size, background color, and web preferences. The loadFile() method loads the index.html file into the window, and openDevTools() opens the DevTools for debugging.
  2. app.whenReady(): This method waits for the Electron app to be ready before executing the specified callback function. In this case, it creates the main window using createWindow().
  3. app.on(‘window-all-closed’): This event handler quits the application when all windows are closed, except on macOS where the application typically remains active.
  4. app.on(‘activate’): This event handler creates a new window when the application is activated (e.g., clicked on the dock icon) and no windows are open.

These functions and event listeners collectively manage the application’s lifecycle, user sessions, window creation, and data retrieval, ensuring smooth operation and interaction between the main and renderer processes.

Note: The webPreferences parameter in the configuration of the browser window will be explained in the Security and Safety section.

Then we’ll create an index.html file next to main.js and review its contents.

<!DOCTYPE html>
 <html lang="en">
 	<meta charset="UTF-8">
   <meta http-equiv="content-security-policy" content="script-src 'self'"/>
 	<title>Desktop QuickBlox Chat</title>
 <h1>Hello world from QuickBlox chat desktop application!</h1>
 <div id="electronChat"></div>
 <script src="./build/js/app.js"></script>

Pay attention to the element:
<meta http-equiv="content-security-policy" content="script-src 'self'"/>

This meta tag defines the content security policy for the page. In this case, it allows loading JavaScript only from the same source (self).

The line, <div id="electronChat"> </div> is a placeholder for react content.

This line, <script src="./build/js/app.js"></script> indicates that we should already have a compiled and built React application located at ‘./build/js/app.js’. Webpack is responsible for this. Below, we’ll go over its settings.

After that, we’ll move on to creating files responsible for the renderer process and configuring Webpack. By this point, our directory structure should look like this:

  • package.json
  • main.js
  • src/
    • js/
      • index.js
      • App.js
  • webpack.common.js

We’ll continue by reviewing the contents of index.js, App.js, and webpack.common.js, and make necessary adjustments to the index.html file.

Content of index.js is:

import React from 'react';

 import { createRoot } from 'react-dom/client';
 import App from "./App";
 const container = document.getElementById('electronChat');
 const root = createRoot(container); // createRoot(container!) if you use TypeScript
 root.render( <App />);

Content of App.js is

import React, {useEffect} from "react";

 export default function App(){
 	const title = "Test ";
 	const extendedTitle = title + 'React App';
 	return (

Content of webpack.common.js is:

 const path = require('path');
 const webpack = require("webpack");

 module.exports = {
 	mode: 'development',
 	entry: './src/js/index.js',
 	devtool: 'inline-source-map',
 	target: 'electron-renderer',
 	module: {
     	rules: [
             	test: /\.js$/,
             	exclude: /node_modules/,
             	use: {
                     loader: 'babel-loader',
                 	options: {
                     	presets: [[
                         	'@babel/preset-env', {
                             	targets: {
                                 	esmodules: true
             	test: [/\.s[ac]ss$/i, /\.css$/i],
             	use: [
                 	// Creates style nodes from JS strings
                 	// Translates CSS into CommonJS
                 	// Compiles Sass to CSS
 	plugins: [
     	new webpack.ProvidePlugin({
         	adapter: ['webrtc-adapter', 'default'],
     	new webpack.ProgressPlugin()
 	resolve: {
     	extensions: ['.js'],
 	output: {
     	filename: 'app.js',
     	path: path.resolve(__dirname, 'build', 'js'),

This is a fairly standard webpack configuration file, with key values being the entry file name, target and the path for the final build location.

Once we complete this part of the work, we’ll be ready to launch and debug our application.

Testing and Debugging

First, we need to make changes to the package.json file. We should build our bundled app.js in the build folder with webpack. Let’s add the following line to the scripts section:

"watch": "webpack --config webpack.common.js --watch"

After that, you can type the following command in the console:

npm run watch

As a result, the app.js will be built, allowing you to use it in index.html.
To run the Electron application, open another command console and type:

npm start

The line win.webContents.openDevTools({ mode: 'detach' }); shows the developer tools in a separate window.

Security and Safety

For detailed information about best practices for security in electron application, check out the following resources:
javascript – Electron – Why do we need to communicate between the main process and the renderer processes? – Stack Overflow

In Electron, interaction between the main and renderer processes is carried out through IPC (Inter-process communication). This is a key element in creating feature-rich desktop applications in Electron. Here are some best practices for interacting between the main and renderer processes:

Using IPC channels: In Electron, processes communicate by passing messages through developer-defined “channels” using the ipcMain and ipcRenderer modules. These channels are arbitrary (you can name them whatever you want) and bidirectional (you can use the same channel name for both modules).

Using preload script: You should be familiar with the idea of using a preload script to import Node.js and Electron modules into the context-isolated renderer process.

Sending messages from the renderer to the main process: To send a unidirectional IPC message from the renderer to the main process, you can use the ipcRenderer.send API to send a message, which is then received by the ipcMain.on API. Typically, this pattern is used to invoke the main process API from your web content.

Security: Calling native GUI-related APIs directly from the renderer is restricted due to security issues and potential resource leaks. Therefore, if you want to do something like read or write a file but don’t want to include Node.js API integration in your renderer processes, you’ll need to send a message from the renderer to the main process (which can be checked and scoped), and the main process will use the Node.js API to read/write the file (again, with limitations).

Implementing Security

To implement all the recommended steps in our project, we need to create a window with the following security parameters:

The webPreferences parameter in the configuration of the browser window (BrowserWindow) defines the web environment settings for the renderer process. These settings include security, context isolation, and preload scripts.

nodeIntegration: false: Disables access to Node.js API in the renderer process, enhancing application security.

contextIsolation: true: Provides context isolation to prevent conflicts of variable names.

worldSafeExecuteJavaScript: true: Prevents execution of unsafe JavaScript code in the renderer process.

preload: path.join(__dirname, 'preload.js'): Specifies the path to the preload script for loading modules and configuring the environment before loading content in the browser window.
These parameters help improve the security and manageability of the renderer process by providing controlled access to system resources and preventing vulnerabilities.

We will implement the preload.js file, inside of which we’ll create an object for subscription.

Content of preload.js is:

 const { contextBridge, ipcRenderer } = require('electron');

 contextBridge.exposeInMainWorld('electronAPI', {
 	requestData: async () => {
     	return new Promise((resolve, reject) => {
         	ipcRenderer.once('response-data', (event, data) => {
         	ipcRenderer.once('response-error', (event, error) => {

To notify main process from renderer side (App.js) we should use:

 async function fetchDataFromMainProcess() {
 	try {
     	// const data = await window.electronAPI.requestData();
     	const data = await electronAPI.requestData();
     } catch (error) {
     	console.error('fetch data from main process error :', error);

On the main process side (main.js) to receive and reply we should use:

 async function prepareDataForRenderer() {
 	try {
// async operation will described in Integration of Quickblox section below
     	const session = await createUserSession();
     	const data = {config:{}, sessionToken: session.token};
     	return data;
 	} catch (error) {
     	throw new Error('Ошибка при получении данных: ' + error.message);

 ipcMain.on('request-data', async (event, arg) => {
 	try {
     	const data = await prepareDataForRenderer();
     	event.reply('response-data', data);
 	} catch (error) {
     	event.reply('response-error', error.message);

Integration of Quickblox JS SDK

Now we are ready to integrate the Quickblox SDK into our project and use it to initialize the React UI Kit. Given that our main process is isolated from the renderer process and represents a Node.js application, we can store all configuration data from our account, such as application keys and secret keys, in the main process.

We will follow this logic to set up the chat application:
Main Process:

  • Create a local copy of the QuickBlox SDK.
  • Initialize the SDK.
  • Authenticate using the admin account.
  • Create a session and obtain its token.
  • Pass the token to the isolated renderer process.

Renderer Process:

  • Receive the token from the main process.
  • Create an isolated copy of the QuickBlox SDK.
  • Initialize the SDK with the token.
  • Connect the React kit.

After completing these steps, you will have a ready-made chat application.

Let’s add new code into main.js

// main processing use node js
 const {app, BrowserWindow, ipcMain } = require('electron');
 const path = require("path");
 const {QuickBlox} = require("quickblox");

 function createWindow(){
 	// renderer processing

 const userRequiredParams = {
   'login': 'YOUR_LOGIN',
   'password': 'YOUR_PASSWORD'
const QBConfig = {
   credentials: {
       appId: -1,
       accountKey: 'YOUR_ACCOUNT_KEY',
       authKey: 'YOUR_AUTH_KEY',
       authSecret: 'YOUR_AUTH_SECRET',
       sessionToken: '',
   configAIApi: {
       AIAnswerAssistWidgetConfig: {
           organizationName: 'Quickblox',
           openAIModel: 'gpt-3.5-turbo',
           apiKey: 'YOUR_OpenAi_API_KEY',
           maxTokens: 3584,
           useDefault: true,
           proxyConfig: {
               api: 'v1/chat/completions',
               servername: 'https://api.openai.com/',
               port: '',
       AITranslateWidgetConfig: {
           organizationName: 'Quickblox',
           openAIModel: 'gpt-3.5-turbo',
           apiKey: 'sYOUR_OpenAi_API_KEY',
           maxTokens: 3584,
           useDefault: true,
           defaultLanguage: 'Ukrainian',
           languages: ['Ukrainian', 'English', 'French', 'Portuguese', 'German'],
           proxyConfig: {
               api: 'v1/chat/completions',
               servername: '',
               port: '',
           // proxyConfig: {
           //   api: 'v1/chat/completions',
           //   servername: 'http://localhost',
           //   port: '3012',
           // },
       AIRephraseWidgetConfig: {
           organizationName: 'Quickblox',
           openAIModel: 'gpt-3.5-turbo',
           apiKey: 'YOUR_OpenAi_API_KEY',
           maxTokens: 3584,
           useDefault: true,
           defaultTone: 'Professional',
           Tones: [],
           proxyConfig: {
               api: 'v1/chat/completions',
               servername: 'https://api.openai.com/',
               port: '',
   appConfig: {
       maxFileSize: 10 * 1024 * 1024,
       sessionTimeOut: 122,
       chatProtocol: {
           active: 2,
       debug: true,
       enableForwarding: true,
       enableReplying: true,
       regexUserName: '^(?=[a-zA-Z])[-a-zA-Z_ ]{3,49}(? {
   let QuickBlox = require('quickblox').QuickBlox;
   const QBOther = new QuickBlox();

   const APPLICATION_ID = QBConfig.credentials.appId;
   const AUTH_KEY = QBConfig.credentials.authKey;
   const AUTH_SECRET = QBConfig.credentials.authSecret;
   const ACCOUNT_KEY = QBConfig.credentials.accountKey;
   const CONFIG = QBConfig.appConfig;


   return new Promise((resolve, reject) => {
       QBOther.createSession(userRequiredParams, (error, result) => {
           if (error) {
           } else {

async function prepareDataForRenderer() {
   try {
       const session = await createUserSession();
       const data = {config:{ ...QBConfig, credentials: {appId: -1,
                                                    accountKey: 'YOUR_ACCOUNT_KEY',
                                                    authKey: '',
                                                    authSecret: '',
                                                    sessionToken: session.token,},},
                        currentUserName: userRequiredParams.login,
                        sessionToken: session.token};
       return data;
   } catch (error) {
       throw new Error('Error: ' + error.message);

 ipcMain.on('request-data', async (event, arg) => {
 	// no changes
 app.whenReady().then( () => {
 	// no changes

 app.on('window-all-closed', ()=>{
     // no changes
 app.on('activate', ()=>{
    // no changes

This code is for an Electron application where the main process is written in Node.js and interacts with a renderer process. Here’s how it works:

  1. The Electron package is imported, along with other necessary modules such as path, Notification, and ipcMain.
  2. A function createWindow() is defined, which is responsible for creating a new browser window in the renderer process. However, the implementation of this function is missing in the provided code snippet.
  3. The createUserSession() function is defined to create a session for a user in QuickBlox. It initializes a QuickBlox instance with the provided credentials, then creates a session using the createSession() method. This function returns a promise that resolves with the session token upon successful creation of the session.
  4. The prepareDataForRenderer() function is defined as an async function. It calls createUserSession() to obtain the session token and then constructs an object containing the session token and any additional configuration data needed for the renderer process.
  5. An event listener is set up using ipcMain.on() to handle requests from the renderer process. When a request with the event name ‘request-data’ is received, it calls prepareDataForRenderer() to fetch the required data. If successful, it sends the data back to the renderer process using event.reply(). If an error occurs, it sends back an error message.
  6. The Electron app is initialized using app.whenReady().then(), which calls the createWindow() function to create the initial window when the app is ready.
  7. Event listeners are set up for the ‘window-all-closed’ and ‘activate’ events to handle window closure and activation, respectively. However, the implementations for these events are missing in the provided code snippet.

Adding a Chat with React UI Kit

Let’s correct data in App.js

import React, {useEffect} from "react";
import QB from "quickblox/quickblox";
import {
} from 'quickblox-react-ui-kit';

export default function App(){
   const qbUIKitContext = useQbUIKitDataContext();
   const [isUserAuthorized, setUserAuthorized] = React.useState(false);
   const [isSDKInitialized, setSDKInitialized] = React.useState(false);
   const [QBConfig, setQBConfig] = React.useState({});
   const [currentUserName, setCurrentUserName] = React.useState('');
   const [sessionToken, setSessionToken] = React.useState('');
   async function fetchDataFromMainProcess() {
       try {
           // const data = await window.electronAPI.requestData();
           const data = await electronAPI.requestData();
       } catch (error) {
           console.error('fetch data from main process error :', error);
   useEffect(() => {
   }, []);
   useEffect(() => {
       if (!sessionToken || sessionToken?.length === 0) return;
       // check if we have installed SDK
       if (typeof window.QB === 'undefined') {
           if (typeof QB !== 'undefined') {
               window.QB = QB;
           } else {
               let QBLib = require('quickblox/quickblox.min');
               window.QB = QBLib;
       if (isSDKInitialized && isUserAuthorized) return;
       console.log('Call on.sessionExpired: ');
       QB.chat.onSessionExpiredListener = function (error) {
           if (error) {
               console.log('onSessionExpiredListener - error: ', error);
           } else {
               console.log('Hello from client app SessionExpiredListener');
       QB.startSessionWithToken(sessionToken, function(err, sessionData){
           if (err){
               console.log('Error startSessionWithToken');
           } else {
               const userId = sessionData.session.user_id;
               const password = sessionData.session.token;
               const paramsConnect = { userId, password };

               QB.chat.connect(paramsConnect, async function (errorConnect, resultConnect) {
                   if (errorConnect) {
                       console.log('Can not connect to chat server: ', errorConnect);
                   } else {
                       const authData = {
                           userId: userId,
                           password: password,
                           userName: currentUserName,
                           sessionToken: sessionData.session.token
                       await qbUIKitContext.authorize(authData);
   }, [sessionToken]);
       return (
           { isSDKInitialized && isUserAuthorized && QBConfig.credentials ?

                       maxFileSize={100 * 1000000}
                           login: currentUserName,
                           password: '',
                               // React states indicating the ability to render UI
                               <QuickBloxUIKitDesktopLayout uikitHeightOffset={'40px'}/>
               <div>wait while SDK is initializing...</div>

This code is a React component that is responsible for setting up a QuickBlox chat application within an Electron environment. Here’s how it works:

  1. The component imports necessary modules and hooks from React and QuickBlox packages.
  2. It defines some state variables using React’s useState hook to manage the application’s state, such as whether the user is authorized, whether the QuickBlox SDK is initialized, and the session token.
  3. The fetchDataFromMainProcess function is defined to fetch data from the main process of the Electron application. This function is called when the component mounts using the useEffect hook.
  4. Another useEffecthook is used to initialize the QuickBlox SDK and establish a connection to the chat server once the session token is available. It checks if the SDK is already installed, initializes it if necessary, and then starts a session with the provided token. If successful, it connects to the chat server and authorizes the user using the obtained session data.
  5. The component renders JSX, which includes the UI elements to display the application’s title, version, and the QuickBlox UI kit. Depending on whether the SDK is initialized and the user is authorized, it either renders the QuickBlox UI kit for chatting or displays a message indicating that the SDK is initializing.
  6. The JSX also includes a QuickBloxUIKitProvider component, which provides necessary configurations and data to the QuickBlox UI kit.

Overall, this component sets up a QuickBlox chat application within an Electron environment, handles user authentication, and manages the state of the application

Wrapping Up

Well done! You should now have a comprehensive overview of how to create a multifunctional desktop communication application!

As a result of our efforts, we have developed a cross-platform desktop version of our open-source Q-municate messenger, equipped with standard chat functionality and capable of running on various operating systems.

We have built this using the application code example from our GitHub repository: https://github.com/QuickBlox/examples/tree/main/Articles/js/electron_chat.

At part of this project we initialized the React UI Kit using the application token, ensuring ease of use. Although we did not include the implementation of Firebase login for simplification purposes, our main goal was to help novice developers create their own cross-platform desktop communication application.

Possible directions for deeper exploration of the topic include:

  • the development of customized chat components
  • improving application security
  • optimizing performance and user experience

We hope that this article will be a useful resource for anyone looking to create their own desktop communication application using modern technologies, and welcome any feedback!

Additional Resources

For further exploration of Quickblox, React, and Electron, we recommend referring to the following useful resources:

Official Quickblox documentation: https://docs.quickblox.com/
Guides on using Quickblox SDK: https://docs.quickblox.com/docs/react-uikit-overview
Code examples and solutions on GitHub Quickblox: https://github.com/QuickBlox/quickblox-javascript-sdk/tree/gh-pages/samples/react-chat-ui-kit-init-with-token-sample

Official React documentation: https://react.dev/ or Getting Started – React (reactjs.org)

Official Electron documentation: https://www.electronjs.org/docs/latest/

Additional tutorials:
How to create your own AI ChatBot using OpenAI and JavaScript SDK
AI-Powered Web App: Part 1, Setting up NodeJS with Express
Integrating AI Features into your React App made Easy
How to send first message in React UI Kit

Have Questions? Need Support?

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

Join QuickBlox Discord

Read More

Ready to get started?

QuickBlox post-box