Cách tạo ứng dụng có sử dụng CSDL trên Firestore Database
Một trang web tĩnh (chỉ có HTML/CSS) giống như một tờ rơi quảng cáo: đẹp nhưng không thể tương tác sâu. Để tạo ra các ứng dụng thực tế như Todo List (Danh sách việc cần làm), App chat, hay trang quản lý bán hàng, bạn cần một nơi để lưu trữ dữ liệu. Theo cách truyền thống, bạn phải thuê máy chủ (Hosting/VPS), cài đặt SQL (MySQL/SQL Server) và viết API để kết nối. Việc này tốn kém và phức tạp.
Cloud Firestore của Google giải quyết vấn đề này bằng cách cung cấp một cơ sở dữ liệu “trên mây” (Serverless). Điểm đặc biệt nhất của nó là tính năng Realtime (Thời gian thực): khi một người dùng thêm dữ liệu, tất cả người dùng khác đang mở trang web sẽ thấy dữ liệu đó hiện lên ngay lập tức mà không cần tải lại trang (F5).

Hôm nay, chúng ta sẽ cùng tạo một ứng dụng “Danh sách công việc chung” để trải nghiệm sức mạnh.
Quy trình gồm 3 bước: Thiết lập Database trên Console, Tạo giao diện, và Viết mã xử lý dữ liệu.
Bước 1: Kích hoạt Firestore trên Firebase Console
Trước khi viết code, ta cần tạo kho chứa dữ liệu.
- Truy cập vào dự án Firebase bạn đã tạo ở bài trước (hoặc tạo mới).
- Ở menu bên trái, chọn Build -> Firestore Database.
- Nhấn Create database.
- Quan trọng: Thiết lập quyền truy cập (Security Rules). Để ứng dụng có thể đọc/ghi dữ liệu ngay lập tức, bạn cần mở quyền truy cập. Hãy chọn 1 trong 2 cách sau:
Cách 1: Chọn chế độ Test Mode (Khuyên dùng khi tạo mới)
Ở bước “Secure rules”, hãy tích chọn Start in test mode (Chế độ thử nghiệm) rồi nhấn Enable.
Tác dụng: Cho phép mọi người đọc/ghi dữ liệu tự do trong 30 ngày. Rất tiện để học tập mà không lo bị chặn quyền.
Cách 2: Sửa thủ công (Nếu lỡ chọn Production Mode hoặc đã tạo rồi)
Sau khi Database được tạo, hãy chuyển sang tab Rules (Quy tắc).
Tại dòng mãallow read, write: if ..., hãy sửa chữfalsethànhtrue.
Dòng lệnh hoàn chỉnh sẽ là:allow read, write: if true;
Nhấn nút Publish để lưu lại.
Tác dụng: Mở khóa hoàn toàn quyền đọc/ghi dữ liệu cho ứng dụng.
Lưu ý chung: Cả 2 cách trên đều mở công khai dữ liệu để thuận tiện cho việc học code. Khi phát triển sản phẩm thực tế, bạn bắt buộc phải cấu hình lại các quy tắc này chặt chẽ hơn để đảm bảo bảo mật. - Chọn vị trí máy chủ (Location). Nên chọn asia-southeast1 (Singapore) để tốc độ về Việt Nam nhanh nhất. Nhấn Enable.
Bước 2: Xây dựng giao diện (File index.html)
Chúng ta tạo một giao diện đơn giản gồm ô nhập công việc và danh sách hiển thị bên dưới.
HTML
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo Firestore Database</title>
<style>
body { font-family: sans-serif; max-width: 500px; margin: 40px auto; padding: 20px; }
.input-group { display: flex; gap: 10px; margin-bottom: 20px; }
input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
ul { list-style: none; padding: 0; }
li { background: #f4f4f4; border-bottom: 1px solid #ddd; padding: 10px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }
.delete-btn { background: #dc3545; padding: 5px 10px; font-size: 0.8em; margin-left: 10px; }
</style>
</head>
<body>
<h2>Danh Sách Công Việc (Realtime)</h2>
<div class="input-group">
<input type="text" id="task-input" placeholder="Nhập công việc cần làm...">
<button id="btn-add">Thêm</button>
</div>
<ul id="task-list">
<li>Đang tải dữ liệu...</li>
</ul>
<script type="module" src="app.js"></script>
</body>
</html>
Bước 3: Lập trình kết nối và xử lý (File app.js)
Đây là phần quan trọng nhất. Chúng ta sẽ thực hiện 2 chức năng chính: Ghi dữ liệu (Add) và Lắng nghe dữ liệu (Listen/Read).
Hãy thay thế firebaseConfig bằng mã của bạn.
JavaScript
// 1. Import Firebase và Firestore
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
import {
getFirestore,
collection,
addDoc,
onSnapshot,
doc,
deleteDoc,
orderBy,
query,
serverTimestamp
} from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js";
// 2. Cấu hình (Copy từ Firebase Console)
const firebaseConfig = {
apiKey: "AIzaSyDcx...",
authDomain: "ten-du-an.firebaseapp.com",
projectId: "ten-du-an",
storageBucket: "ten-du-an.appspot.com",
messagingSenderId: "...",
appId: "..."
};
// 3. Khởi tạo App và Database
const app = initializeApp(firebaseConfig);
const db = getFirestore(app); // Biến 'db' đại diện cho cơ sở dữ liệu
// 4. Lấy các thẻ HTML
const taskInput = document.getElementById('task-input');
const btnAdd = document.getElementById('btn-add');
const taskList = document.getElementById('task-list');
// --- CHỨC NĂNG 1: THÊM DỮ LIỆU (CREATE) ---
btnAdd.addEventListener('click', async () => {
const taskContent = taskInput.value;
if (taskContent.trim() === "") return alert("Vui lòng nhập nội dung!");
try {
// Thêm một 'document' mới vào 'collection' tên là 'tasks'
await addDoc(collection(db, "tasks"), {
content: taskContent,
createdAt: serverTimestamp() // Lưu thời gian tạo để sắp xếp
});
taskInput.value = ""; // Xóa ô nhập sau khi thêm
console.log("Đã thêm công việc!");
} catch (e) {
console.error("Lỗi khi thêm: ", e);
}
});
// --- CHỨC NĂNG 2: ĐỌC DỮ LIỆU THỜI GIAN THỰC (READ - REALTIME) ---
// Thay vì dùng getDocs (chỉ lấy 1 lần), ta dùng onSnapshot (luôn lắng nghe thay đổi)
// Tạo câu lệnh truy vấn: Lấy collection 'tasks' và sắp xếp theo thời gian tạo mới nhất
const q = query(collection(db, "tasks"), orderBy("createdAt", "desc"));
onSnapshot(q, (snapshot) => {
// Hàm này sẽ TỰ ĐỘNG chạy mỗi khi dữ liệu trên Server thay đổi
taskList.innerHTML = ""; // Xóa danh sách cũ để vẽ lại danh sách mới
snapshot.forEach((doc) => {
const task = doc.data();
const taskId = doc.id; // Lấy ID của document để dùng cho nút xóa
// Tạo mã HTML cho từng dòng
const li = document.createElement('li');
li.innerHTML = `
<span>${task.content}</span>
<button class="delete-btn" data-id="${taskId}">Xóa</button>
`;
taskList.appendChild(li);
});
// Gắn sự kiện xóa cho các nút vừa tạo
addDeleteEvents();
});
// --- CHỨC NĂNG 3: XÓA DỮ LIỆU (DELETE) ---
function addDeleteEvents() {
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => {
btn.addEventListener('click', async (e) => {
const idToDelete = e.target.getAttribute('data-id');
// Xóa document dựa trên ID
if(confirm("Bạn muốn xóa mục này?")) {
await deleteDoc(doc(db, "tasks", idToDelete));
}
});
});
}
Giải thích các thuật ngữ quan trọng:
- Collection (Bộ sưu tập): Giống như một cái Bảng (Table) trong SQL hay một thư mục. Ở đây ta đặt tên là
tasks. - Document (Tài liệu): Giống như một hàng (Row) trong bảng hay một file chứa dữ liệu. Mỗi công việc là một document.
- onSnapshot: Đây là “trái tim” của ứng dụng realtime. Nó giữ kết nối liên tục với server. Chỉ cần bạn mở 2 tab trình duyệt, thêm dữ liệu ở tab này, tab kia sẽ tự cập nhật ngay lập tức.
Như vậy, bạn đã hoàn thành một ứng dụng Full-stack (có cả giao diện và cơ sở dữ liệu) mà không cần xây dựng server backend phức tạp. Với Firestore, việc lưu trữ, đồng bộ dữ liệu trở nên cực kỳ đơn giản và tốc độ.
Hướng phát triển tiếp theo: Để ứng dụng hoàn thiện hơn, bạn có thể kết hợp bài viết này với bài hướng dẫn Đăng nhập (Authentication) trước đó để làm các chức năng như:
- Dữ liệu riêng tư: Chỉ hiển thị công việc của chính người đăng nhập (Sử dụng
wheretrong câu truy vấn). - Phân quyền: Chỉ cho phép Admin xóa dữ liệu.
- Hồ sơ người dùng: Lưu thông tin avatar, ngày sinh của người dùng vào Firestore khi họ đăng ký tài khoản.
Chúc bạn thành công với ứng dụng đầu tay trên nền tảng Cloud!