Building a Realtime Chat application with Meteor.js framework
Introduction
Meteor (Meteor.js) is a JavaScript Full-Stack framework built on Node.js. It helps in developing web and mobile applications quickly and efficiently. With its design optimized for smooth integration between the client and server, Meteor not only simplifies the process of sharing code but also enhances the experience with real-time data.
If you’re looking for a quick app development solution or want to expand your knowledge of modern web technologies, this article will give you a comprehensive and deep understanding of Meteor.
Outstanding features of Meteor
In this section, we’ll dive deeper into the outstanding features of Meteor, which make this framework unique and distinct in the web and mobile development field:
Smooth Integration between Client and Server: Meteor provides a reactive data model that allows instant data updates on both client and server without needing complex data synchronization code.
Code Sharing between Client and Server: With Meteor, sharing code becomes easy, optimizing the development and maintenance process of applications.
Real-Time Data Updates: Meteor uses the Distributed Data Protocol (DDP) to synchronize data between client and server, making app data updates quick and smooth.
Strong Ecosystem: Meteor has a rich ecosystem with thousands of packages and tools, from integrating with MongoDB to deploying apps with Galaxy, Meteor’s hosting service.
Hot Code Push Feature: This feature allows developers to update their apps without disrupting the user experience, a significant benefit in maintaining and updating applications.
Cross-Platform: Code once, and you can deploy it as a web app, or build it into a mobile app for Android, or iOS.
Getting Started with Meteor.js
To start with Meteor, make sure you have Node.js installed on your computer. Currently, Meteor supports Node.js versions 10 to 14, with Meteor 3.0 being developed to support the latest versions of Node.js.
Installing Meteor
Windows, Linux, and OS X:
You can install Meteor on all these platforms using the following command in the terminal:
npm install -g meteor
Also, on Linux and OS X, Meteor can be installed via curl
using the following command:
curl https://install.meteor.com/ | sh
Creating a Simple Realtime Chat Application
Let’s learn more about Meteor.js by creating a simple real-time chat project.
1. Initializing the Project:
For a quick setup, use the meteor create
command with the --blaze
option and name your project. In this case, we’ll call the project “simple-chat-meteor”:
meteor create --blaze simple-chat-meteor
After successful initialization, you will receive the following message in the terminal:
To run the app, move to the project folder and use the command meteor run
:
cd simple-chat-meteor meteor run
Open a browser and go to http://localhost:3000 to see your app.
2. Project Structure:
.meteor: This is the most important folder in any Meteor app. It contains the core configuration files of your Meteor application and subfolders like:
- local: Contains local data and log files for your app.
- packages: Lists the libraries your app uses.
- platforms: The platforms your app supports.
- versions: Contains the versions of the libraries your app is using.
client: Contains the client-side code of the app and usually includes:
- main.css: The main CSS file for client styles.
- main.html: The main HTML file, usually the starting point of the app with layout and template definitions.
- main.css: The main JavaScript file for the client, where you define events and helpers for Blaze templates.
server: Contains the server-side code of the app and typically includes:
- main.js: The main JavaScript file for the server, where you set up publications and methods as well as server initialization configurations.
tests: Contains your app’s test files.
node_modules: Contains the NPM packages your project depends on.
3. Implementing Features:
In this section, we’ll go through the necessary steps to add functionality to our Meteor chat app, including setting up the user interface, handling events, and building login, and logout functionality.
User Interface Setup
In the “client/main.html” file: We’ll define the basic HTML structure for your chat app, including the title and meta-tags to optimize display on mobile devices.
<head> <title>Simple Chat Meteor</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta charset="utf-8"/> <meta http-equiv="x-ua-compatible" content="ie=edge"/> <meta name="viewport" content="width=device-width, height=device-height, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <meta name="mobile-web-app-capable" content="yes"/> <meta name="apple-mobile-web-app-capable" content="yes"/> </head>
In the “client/main.js“, we import the “Chat.js” file to initialize the chat UI.
import '../imports/ui/Chat/Chat.js';
Event and Data Handling
Create an “imports” folder to contain code divided by UI and logic.
Inside “imports/ui/Chat”, create a “Chat.html” file to define the interface of the chat application. We’ll use Blaze templates to organize content and display logic:
<body> {{> chatContainer}} </body> <template name="chatContainer"> <div class="chat-container"> <div class="chat-header"> <h2>Simple Chat Meteor</h2> </div> {{> chatContent}} {{> chatInput}} </div> </template> <template name="chatContent"> <div class="chat-content"> {{#each chats}} <div class="message"> <div class="username">{{ username }}</div> <div class="message-content">{{ messageText }}</div> </div> {{/each}} </div> </template> <template name="chatInput"> <div class="chat-input"> <input id="message" type="text" placeholder="Input your message..." /> <button id="sendMessage">Send</button> </div> </template>
chatContainer: The main template contains the entire chat application interface, including the app’s title (chat-header) and chat content.
chatContent: A smaller template used to display chat content. It iterates through each “chats” (message), showing the “username” (sender’s name) and “messageText” (message content).
chatInput: Another template allowing users to send new messages. It includes an input field for typing messages and a “Send” button to send the message.
Add CSS in the “client/main.css” file:
:root { --main-bg-color: #f0f2f5; --chat-bg-color: #fff; --highlight-color: #f69914; --text-color-white: #fff; --border-color: #ddd; --input-border-color: #ccc; --hover-bg-color: #0056b3; } body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; margin: 0; background-color: var(--main-bg-color); } .chat-container { height: 100%; width: 600px; border: 1px solid var(--border-color); background-color: var(--chat-bg-color); display: flex; flex-direction: column; } .chat-header { display: flex; justify-content: space-between; align-items: center; background-color: var(--highlight-color); color: var(--text-color-white); height: 68px; padding: 0px 10px; } .chat-header button#logout { height: 30px; background-color: transparent; cursor: pointer; color: var(--text-color-white); border: 1px solid var(--chat-bg-color); border-radius: 10px; } .chat-content { padding: 10px; height: calc(100vh - 90px - 57px); overflow-y: auto; gap: 10px; } .chat-content .message { background-color: #f1f1f1; padding: 10px; border-radius: 10px; display: flex; flex-direction: column; } .chat-content .message:not(:first-child) { margin-top: 10px; } .chat-content .message .username { font-weight: bold; color: var(--highlight-color); } .chat-content .message .message-content { margin-top: 5px; } .chat-input { display: flex; padding: 10px; height: 57px; box-sizing: border-box; } .chat-input input { flex: 1; padding: 10px; margin-right: 10px; border: 1px solid var(--input-border-color); border-radius: 4px; } .chat-input button { padding: 10px; background-color: var(--highlight-color); color: white; border: none; border-radius: 4px; cursor: pointer; } .chat-input button:hover { background-color: var(--hover-bg-color); } .auth-container { background-color: white; padding: 40px; border-radius: 5px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); width: 300px; } .auth-container h2 { text-align: center; margin-bottom: 20px; } .auth-container p span { text-decoration: underline; color: var(--hover-bg-color); cursor: pointer; } #loginForm .input-group, #signupForm .input-group { margin-bottom: 15px; display: flex; flex-direction: column; } #loginForm .input-group label, #signupForm .input-group label { margin-bottom: 5px; } #loginForm .input-group input, #signupForm .input-group input { padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; } #loginForm button, #signupForm button { background-color: orange; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; width: 100%; } #loginForm button:hover, #signupForm button:hover { background-color: darkorange; }
In the “imports/api” folder, create a “ChatsCollection.js” file:
import { Mongo } from 'meteor/mongo'; export const ChatsCollection = new Mongo.Collection('chats');
In the “imports/api” folder, create a “ChatsPublications.js” file:
import { Meteor } from "meteor/meteor"; import { ChatsCollection } from "./ChatsCollection"; Meteor.publish("chats", function () { return ChatsCollection.find({}, { sort: { createdAt: -1 } }); });
Next, we’ll connect to the Meteor chat database, subscribe to chat data from the server, and use it to display messages in chronological order in the user interface.
Add the following code to the “imports/ui/Chat/Chat.js” file:
import { Template } from "meteor/templating"; import { ChatsCollection } from "/imports/api/ChatsCollection"; import "./Chat.html"; Meteor.subscribe("chats"); Template.chatContent.helpers({ chats() { return ChatsCollection.find({}, { sort: { createdAt: 1 } }); }, });
Replace the code in the “server/main.js” file as follows:
import { Meteor } from "meteor/meteor"; import { ChatsCollection } from "/imports/api/ChatsCollection"; import '/imports/api/ChatsPublications'; Meteor.startup(() => { if (ChatsCollection.find().count() === 0) { ChatsCollection.insert({ messageText: 'Welcome to the chat app!', createdAt: new Date(), username: "Admin", }); } });
The goal is to check if there are no messages when the server starts, and then add a default message.
→ Start the Meteor.js application with meteor run
and go to http://localhost:3000 to see the result.
Implementing the Send Message Functionality:
In the “imports/ui/Chat/Chat.js” file, add the following code:
... Template.body.events({ "click #sendMessage": function () { const messageElement = document.querySelector("#message"); if (messageElement.value.trim()) { Meteor.call("chats.sendMessage", messageElement.value.trim()); messageElement.value = ""; } }, });
In the “imports/api” folder, create a “ChatsMethods.js” file and add the following code:
import { Meteor } from 'meteor/meteor'; import { ChatsCollection } from "./ChatsCollection"; Meteor.methods({ 'chats.sendMessage'(message) { ChatsCollection.insert({ messageText: message, createdAt: new Date(), username: "Admin" }); } });
Next, import the newly created method into the “server/main.js” file:
... import '/imports/api/ChatsMethods'; ...
Let’s try sending a message now.
Implementing Login, Register, and Logout Functions:
We will install the “accounts-password” and “bcrypt” packages:
meteor add accounts-password meteor npm install --save bcrypt
In the “imports/ui/Auth” folder, create an “Auth.html” file and add the following code:
<template name="authContainer"> {{> Template.dynamic template=currentView}} </template> <template name="loginContainer"> <div class="auth-container" > <h2>Login</h2> <form id="loginForm"> <div class="input-group"> <label for="username">Username</label> <input type="text" id="username" name="username" required> </div> <div class="input-group"> <label for="password">Password</label> <input type="password" id="password" name="password" required> </div> <button type="submit">Login</button> </form> <p>Don’t have an account yet? <span>Sign up</span> </p> </div> </template> <template name="signupContainer"> <div class="auth-container" > <h2>Sign up</h2> <form id="signupForm"> <div class="input-group"> <label for="username">Username</label> <input type="text" id="username" name="username" required> </div> <div class="input-group"> <label for="password">Password</label> <input type="password" id="password" name="password" required> </div> <div class="input-group"> <label for="confirmPassword">Confirm Password</label> <input type="password" id="rePassword" name="confirmPassword" required> </div> <button type="submit">Sign up</button> </form> <p>Already have an account? <span>Login here</span> </p> </div> </template>
In the “imports/ui/Auth” folder, create an “Auth.js” file and add the following code:
import { Template } from "meteor/templating"; import "./Auth.html"; import { ReactiveVar } from "meteor/reactive-var"; currentView = new ReactiveVar("loginContainer"); Template.authContainer.helpers({ currentView: function () { return currentView.get(); }, }); Template.loginContainer.events({ "click span": function (e) { e.preventDefault(); currentView.set("signupContainer"); }, "submit #loginForm"(e) { e.preventDefault(); const target = e.target; const username = target.username.value; const password = target.password.value; Meteor.loginWithPassword(username, password, (err) => { if (err) { alert(err); } }); }, }); Template.signupContainer.events({ "click span": function (e) { e.preventDefault(); currentView.set("loginContainer"); }, "submit #signupForm"(e) { e.preventDefault(); const target = e.target; const username = target.username.value; const password = target.password.value; const confirmPassword = target.confirmPassword.value; if (password !== confirmPassword) { alert("Password do not match"); return; } Accounts.createUser( { username, password, }, (err) => { if (err) { alert(err); } } ); }, });
In the “imports/ui/Chat/Chat.html” file, we will check if the user is logged in and add a logout button:
<body> {{#if isUserLogged}} {{> chatContainer}} {{else}} {{> authContainer}} {{/if}} </body> ... <div class="chat-header"> <h2>Simple Chat Meteor</h2> <button id="logout">Logout</button> </div> ...
In the “imports/ui/Chat/Chat.js” file, we will check if the user is logged in and handle the logout:
import "../Auth/Auth.js" ... Template.body.helpers({ isUserLogged() { return !!Meteor.userId() && !Meteor.loggingIn(); }, }); Template.body.events({ "click #sendMessage": function () { const messageElement = document.querySelector("#message"); if (messageElement.value.trim()) { Meteor.call("chats.sendMessage", messageElement.value.trim()); messageElement.value = ""; } }, "click #logout": function () { Meteor.logout(); }, });
In the “server/main.js” file, check and add an “admin” account if it doesn’t exist, and add a “username” for the default “message“:
import { Meteor } from "meteor/meteor"; import '/imports/api/ChatsMethods'; import '/imports/api/ChatsPublications'; import { ChatsCollection } from "/imports/api/ChatsCollection"; import { Accounts } from 'meteor/accounts-base'; const SEED_USERNAME = "admin"; const SEED_PASSWORD = "admin"; Meteor.startup(() => { if (!Accounts.findUserByUsername(SEED_USERNAME)) { Accounts.createUser({ username: SEED_USERNAME, password: SEED_PASSWORD, }); } const user = Accounts.findUserByUsername(SEED_USERNAME); if (ChatsCollection.find().count() === 0) { ChatsCollection.insert({ messageText: 'Welcome to the chat app!', createdAt: new Date(), username: user.username, }); } });
Similarly, add a “username” for the “sendMessage” method in the “imports/api/ChatsMethods.js” file:
import { Meteor } from 'meteor/meteor'; import { ChatsCollection } from "./ChatsCollection"; Meteor.methods({ 'chats.sendMessage'(message) { const username = Meteor.user()?.username; ChatsCollection.insert({ messageText: message, createdAt: new Date(), username: username }); } });
Let’s see the results now:
Before logging in, the Login screen is displayed.
The Sign-up screen.
Comparing with Express.js
Scalability
- Meteor.js: Offering a “model kit” with built-in features and components, Meteor.js makes the development process quicker and easier. However, this can limit customization and scalability in some large projects.
- Express.js: As a flexible framework, Express.js provides an environment that allows developers the freedom to build their application structure as desired, making it ideal for large and complex projects with high scalability requirements.
Real-Time Communication
- Meteor.js: Equipped with built-in real-time communication between client and server, it ensures data is updated instantly without needing to reload the page.
- Express.js: Achieving similar functionality requires integration with third-party libraries, adding to the development effort.
Database Integration
- Meteor.js: Tightly integrated with MongoDB and optimized for its use, making data management straightforward but limiting database choices.
- Express.js: Supports connection with a variety of databases, from MySQL to MongoDB, allowing flexibility based on project needs.
Community and Ecosystem
- Meteor.js: Although the community is smaller, it still offers sufficient packages and tools to support development needs.
- Express.js: Has a large and diverse community with thousands of libraries and tools, providing many options to expand and enhance applications.
Development Speed and Learning Difficulty
- Meteor.js: Accelerates application development by integrating many functions, reducing configuration and management work. The learning path is relatively accessible and suitable for beginners.
- Express.js: Offers a flexible and highly customizable approach, which might take more time to set up but benefits long-term project development. Requires an understanding of Node.js and how to integrate middleware, suitable for experienced developers.
Conclusion
From the analysis above, it’s clear that Express.js and Meteor both have their distinct advantages and disadvantages, suitable for different types of projects and development needs:
Express.js is a good choice for projects requiring high customization and scalability. It’s especially suited for complex projects, diverse in database requirements, and demands a large ecosystem with many library and tool options.
Conversely, Meteor.js is the perfect choice for rapid and easy development, especially for real-time applications and small to medium projects that rely on MongoDB. It suits developers wanting to quickly deploy ideas without spending too much time on configuration and infrastructure management.
In summary, choosing between Express.js and Meteor depends on the specific requirements of the project, the skills, and experience of the development team, as well as the goals and timeline of the project. Both technologies have their unique strengths, and choosing the right technology will play a significant part in the project’s success.