Xây dựng ứng dụng Chat Realtime với framework Meteor.js

Giới thiệu

Meteor (Meteor.js) là một framework JavaScript Full-Stack, được xây dựng dựa trên Node.js, giúp phát triển các ứng dụng web và mobile một cách nhanh chóng và hiệu quả. Với thiết kế tối ưu hóa cho sự tích hợp mượt mà giữa client và server, Meteor không chỉ giúp đơn giản hóa quá trình chia sẻ code mà còn nâng cao trải nghiệm với dữ liệu thời gian thực.

Nếu bạn đang tìm kiếm một giải pháp phát triển ứng dụng nhanh chóng hoặc muốn mở rộng kiến thức về các công nghệ web hiện đại, bài viết này sẽ cung cấp cái nhìn tổng quan và sâu sắc về Meteor.

Đặc điểm nổi bật của Meteor

Trong phần này, chúng ta sẽ khám phá sâu hơn về các đặc điểm nổi bật của Meteor, làm nên sự khác biệt và độc đáo của framework này trong lĩnh vực phát triển web và mobile:

Tích Hợp Mượt Mà giữa Client và Server: Meteor cung cấp một mô hình dữ liệu đồng bộ tự động, cho phép cập nhật dữ liệu tức thời trên cả client và server mà không cần phải viết code đồng bộ dữ liệu phức tạp.

Chia Sẻ Code giữa Client và Server: Với Meteor, việc chia sẻ code trở nên dễ dàng, giúp tối ưu hóa quy trình phát triển và duy trì ứng dụng.

Cập Nhật Dữ liệu Thời Gian Thực: Meteor sử dụng DDP (Distributed Data Protocol) để đồng bộ dữ liệu giữa client và server, giúp ứng dụng cập nhật dữ liệu một cách nhanh chóng và mượt mà.

Hệ Sinh Thái Mạnh Mẽ: Meteor có một hệ sinh thái phong phú với hàng ngàn gói và công cụ hỗ trợ, từ việc tích hợp với MongoDB cho đến việc triển khai ứng dụng với Galaxy, dịch vụ hosting của chính Meteor.

Tính Năng Hot Code Push: Tính năng này cho phép các nhà phát triển cập nhật ứng dụng của họ mà không làm gián đoạn trải nghiệm người dùng, một lợi ích lớn trong việc duy trì và cập nhật ứng dụng.

Đa Nền Tảng: Chỉ cần code một lần, bạn có thể deploy nó thành một web app, hoặc build nó thành một mobile app trên Android, IOS.

Bắt đầu với Meteor.js

Để bắt đầu với Meteor, bạn cần đảm bảo rằng Node.js đã được cài đặt trên máy của bạn. Hiện tại, Meteor hỗ trợ Node.js từ phiên bản 10 đến 14, với Meteor 3.0 đang trong quá trình phát triển để hỗ trợ phiên bản Node.js mới nhất.

Cài đặt Meteor

Windows, Linux, và OS X:

Bạn có thể cài đặt Meteor trên tất cả các nền tảng này bằng lệnh sau trong terminal:

npm install -g meteor

Ngoài ra, trên Linux và OS X, Meteor cũng có thể được cài đặt qua curl bằng cách sử dụng lệnh sau:

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

Tạo Ứng Dụng Chat Realtime Đơn Giản

Chúng ta sẽ cùng tìm hiểu thêm về Meteor.js bằng cách thực hiện 1 project chat realtime đơn giản nhé.

1. Khởi Tạo Dự Án:

Để thiết lập nhanh chóng, sử dụng lệnh meteor create kèm theo tùy chọn --blaze và đặt tên cho dự án của bạn. Trong trường hợp này, chúng ta sẽ gọi dự án là “simple-chat-meteor”:

meteor create --blaze simple-chat-meteor

Sau khi khởi tạo thành công, bạn sẽ nhận được thông báo sau trong terminal:

Để chạy ứng dụng, di chuyển vào thư mục dự án và sử dụng lệnh meteor run:

cd simple-chat-meteor
meteor run

Mở trình duyệt và truy cập http://localhost:3000 để xem ứng dụng của bạn.

2. Cấu trúc dự án:

.meteor: Là thư mục quan trọng nhất trong bất kỳ ứng dụng Meteor nào. Nó chứa các tệp cấu hình cốt lõi của ứng dụng Meteor và các thư mục con như:

  • local: Chứa dữ liệu cục bộ và tệp nhật ký cho ứng dụng của bạn.
  • packages: Danh sách các thư viện mà ứng dụng của bạn sử dụng.
  • platforms: Các nền tảng mà ứng dụng của bạn hỗ trợ.
  • versions: Chứa phiên bản của các thư viện mà ứng dụng của bạn đang sử dụng.

client: Thư mục này dùng để chứa mã nguồn phía client của ứng dụng và thường bao gồm:

  • main.css: Tệp CSS chính cho styles của client.
  • main.html: Tệp HTML chính, thường là điểm khởi đầu của ứng dụng với các định nghĩa layout và template.
  • main.js: Tệp JavaScript chính cho client, nơi bạn định nghĩa các sự kiện và helpers cho templates của Blaze.

server: Thư mục này dùng để chứa mã nguồn phía server của ứng dụng và thường bao gồm:

  • main.js: Tệp JavaScript chính cho server, nơi bạn thiết lập các publication và methods cũng như các cấu hình khởi tạo server.

tests: Thư mục này dùng để chứa các bài kiểm thử cho ứng dụng của bạn.

node_modules: Thư mục này dùng để chứa các gói NPM mà dự án của bạn phụ thuộc vào.

3. Thực hiện các chức năng:

Trong phần này, chúng ta sẽ đi qua các bước cần thiết để thêm chức năng vào ứng dụng chat Meteor của chúng ta, bao gồm cấu hình giao diện người dùng, xử lý sự kiện, và xây dựng chức năng đăng nhập, đăng xuất.

Cấu Hình Giao Diện Người Dùng

Ở file “client/main.html”: Chúng ta sẽ định nghĩa cấu trúc HTML cơ bản cho ứng dụng chat của bạn, bao gồm tiêu đề và các meta-tag để tối ưu hóa hiển thị trên thiết bị di động.

<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>

Ở file “client/main.js“, chúng ta import file “Chat.js” để khởi tạo UI chat.

import '../imports/ui/Chat/Chat.js';

Xử Lý Sự Kiện và Dữ Liệu

Tạo thư mục “imports” để chứa mã nguồn phân chia theo UI và logic.

Trong “imports/ui/Chat”, tạo file “Chat.html” để định nghĩa giao diện của ứng dụng chat.

Chúng ta sẽ sử dụng Blaze templates để tổ chức nội dung và logic hiển thị:

<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: Đây là template chính chứa toàn bộ giao diện của ứng dụng chat, bao gồm tiêu đề của ứng dụng (chat-header) và phần nội dung chat.

chatContent: Một template nhỏ hơn, được sử dụng để hiển thị nội dung chat. Nó lặp qua mỗi “chats” (các tin nhắn), hiển thị “username” (tên người gửi) và “messageText” (nội dung tin nhắn).

chatInput: Một template khác cho phép người dùng gửi tin nhắn mới. Bao gồm một trường input để nhập tin nhắn và một nút “Send” để gửi tin nhắn.

Thêm css tại file “client/main.css”:

: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;
}

Trong thư mục “imports/api” tạo file “ChatsCollection.js

import { Mongo } from 'meteor/mongo';
 
export const ChatsCollection = new Mongo.Collection('chats');

Trong thư mục “imports/api” tạo file “ChatsPublications.js

import { Meteor } from "meteor/meteor";

import { ChatsCollection } from "./ChatsCollection";

Meteor.publish("chats", function () {
  return ChatsCollection.find({}, { sort: { createdAt: -1 } });
});

Tiếp theo, chúng ta sẽ kết nối với cơ sở dữ liệu chat trong Meteor, đăng ký dữ liệu chat từ server và sử dụng nó để hiển thị các tin nhắn theo thứ tự thời gian trong giao diện người dùng.

Thêm đoạn Code sau vào file “imports/ui/Chat/Chat.js”:

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 } });
  },
});

Thay thế đoạn code của file “server/main.js” như sau:

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",
    });
  }
});

Mục đích là để khi khởi động server, thì sẽ kiểm tra nếu như chưa có message nào thì sẽ thêm vào message mặc định.

→ Khởi động ứng dụng Meteor.js bằng lệnh meteor run và truy cập vào địa chỉ http://localhost:3000 để xem kết quả.

Thực hiện chức năng gửi tin nhắn:

Trong file “imports/ui/Chat/Chat.js”, thêm đoạn code sau:

...

Template.body.events({
  "click #sendMessage": function () {
    const messageElement = document.querySelector("#message");
    if (messageElement.value.trim()) {
      Meteor.call("chats.sendMessage", messageElement.value.trim());
      messageElement.value = "";
    }
  },
});

Trong thư mục “imports/api”, tạo file “ChatsMethods.js” và thêm đoạn code sau:

import { Meteor } from 'meteor/meteor';
import { ChatsCollection } from "./ChatsCollection";

Meteor.methods({
    'chats.sendMessage'(message) {  
      ChatsCollection.insert({
        messageText: message,
        createdAt: new Date(),
        username: "Admin"
      });
    }
});

Tiếp theo, chúng ta sẽ import method vừa mới tạo vào file “server/main.js”:

...
import '/imports/api/ChatsMethods';
...

Hãy thử gửi 1 tin nhắn bất kì nhé:

Thực hiện chức năng Login, Register và Logout:

Chúng ta sẽ cài đặt packages “accounts-password và “bcrypt”:

meteor add accounts-password
meteor npm install --save bcrypt

Tại thư mục “imports/ui/Auth” tạo file “Auth.html” và thêm đoạn code sau:

<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>

Tại thư mục “imports/ui/Auth” tạo file “Auth.js” và thêm đoạn code sau:

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);
        }
      }
    );
  },
});

Ở file “imports/ui/Chat/Chat.html” chúng ta sẽ tiến hành kiểm tra xem user đã login hay chưa, và thêm nút logout:

<body>
    {{#if isUserLogged}} {{> chatContainer}} {{else}} {{> authContainer}} {{/if}}
</body>

...
    <div class="chat-header">
        <h2>Simple Chat Meteor</h2>
        <button id="logout">Logout</button>
    </div>
...

Ở file “imports/ui/Chat/Chat.js”, chúng ta sẽ kiểm tra xem user đã login hay chưa, và xử lý 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();
  },
});

Ở file “server/main.js”,  kiểm tra và thêm account “admin” nếu nó không tồn tại, và thêm “username” cho “message” mặc định:

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,
    });
  }
});

Tương tự, chúng ta cũng thêm “username” cho methods “sendMessage” ở file “imports/api/ChatsMethods.js”:

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
    });
  }
});

Cùng xem thành quả nhé:

Ở trạng thái chưa đăng nhập, hiển thị màn hình Login.

Màn hình Sign up

So sánh với Express.js

Khả Năng Mở Rộng

  • Meteor.js: Cung cấp một “bộ công cụ phát triển” với các tính năng và bộ phận đã được xây dựng sẵn, Meteor.js giúp quá trình phát triển nhanh chóng và dễ dàng hơn. Tuy nhiên, điều này có thể giới hạn khả năng tùy chỉnh và mở rộng ở một số dự án lớn.
  • Express.js: Là một framework linh hoạt, Express.js cung cấp môi trường cho phép các nhà phát triển tự do xây dựng cấu trúc ứng dụng theo ý muốn, làm cho nó trở nên lý tưởng cho các dự án lớn và phức tạp với yêu cầu mở rộng cao.

Giao Tiếp Thời Gian Thực

  • Meteor.js: Được trang bị sẵn khả năng giao tiếp thời gian thực giữa máy khách và máy chủ, giúp dữ liệu được cập nhật ngay lập tức mà không cần tải lại trang.
  • Express.js: Để đạt được tính năng tương tự, cần phải tích hợp thêm các thư viện bên thứ ba, tăng thêm công số phát triển.

Tích Hợp Cơ Sở Dữ Liệu

  • Meteor.js: Tích hợp chặt chẽ với MongoDB và tối ưu cho việc sử dụng nó, giúp quản lý dữ liệu dễ dàng nhưng giới hạn lựa chọn.
  • Express.js: Hỗ trợ kết nối với đa dạng cơ sở dữ liệu, từ SQL đến NoSQL, cho phép linh hoạt chọn lựa dựa trên nhu cầu dự án.

Cộng Đồng và Hệ Sinh Thái

  • Meteor.js: Dù cộng đồng nhỏ hơn, nhưng vẫn cung cấp đủ các gói và công cụ để hỗ trợ nhu cầu phát triển.
  • Express.js: Có một cộng đồng lớn và đa dạng với hàng ngàn thư viện và công cụ bổ sung, cung cấp nhiều lựa chọn để mở rộng và cải thiện ứng dụng.

Tốc Độ Phát Triển và Độ Khó Học

  • Meteor.js: Tăng tốc độ phát triển ứng dụng bằng cách tích hợp sẵn nhiều chức năng, giảm thiểu công việc cấu hình và quản lý. Lộ trình học tương đối dễ tiếp cận, thích hợp cho người mới bắt đầu.
  • Express.js: Cung cấp cách tiếp cận linh hoạt và tùy chỉnh cao, có thể mất thêm thời gian để thiết lập nhưng mang lại lợi ích về lâu dài khi phát triển dự án. Đòi hỏi sự hiểu biết về Node.js và cách thức tích hợp các middleware, phù hợp với những nhà phát triển có kinh nghiệm.

Kết luận

Từ những phân tích trên, có thể thấy rằng Express.js và Meteor đều có những ưu và nhược điểm riêng biệt, phù hợp với các loại dự án và nhu cầu phát triển khác nhau:

Express.js là lựa chọn tốt cho những dự án cần mức độ tùy chỉnh cao và khả năng mở rộng lớn. Nó đặc biệt phù hợp với những dự án phức tạp, đa dạng về cơ sở dữ liệu và yêu cầu một hệ sinh thái lớn với nhiều lựa chọn thư viện và công cụ.

Ngược lại, Meteor.js là sự chọn lựa hoàn hảo cho việc phát triển nhanh chóng và dễ dàng, đặc biệt là với các ứng dụng thời gian thực và dự án nhỏ đến trung bình cần đến MongoDB. Nó phù hợp với các nhà phát triển muốn tập trung vào việc triển khai ý tưởng nhanh chóng mà không mất quá nhiều thời gian vào cấu hình và quản lý hạ tầng.

Tóm lại, lựa chọn giữa Express.js và Meteor phụ thuộc vào yêu cầu cụ thể của dự án, kỹ năng và kinh nghiệm của đội ngũ phát triển, cũng như mục tiêu và khung thời gian dự án. Cả hai công nghệ đều có những điểm mạnh riêng biệt, và việc lựa chọn công nghệ phù hợp sẽ góp phần quan trọng vào sự thành công của dự án.

Tài liệu tham khảo

https://docs.meteor.com/

https://www.blazejs.org/

https://expressjs.com/