Giới thiệu

Firebase Firestore là một cơ sở dữ liệu đám mây - nghĩa là dữ liệu của bạn được lưu trữ trên máy chủ của Google, không phải trên máy tính của bạn. Nó giúp bạn lưu trữ và quản lý dữ liệu một cách an toàn và hiệu quả.
Cơ sở dữ liệu là gì? - Hãy tưởng tượng nó như một chiếc tủ có nhiều ngăn (collections) và trong mỗi ngăn có nhiều tài liệu (documents) chứa thông tin chi tiết. Firestore giúp bạn tổ chức, lưu trữ và tìm kiếm thông tin này một cách dễ dàng.

Cấu trúc cơ bản: Collections và Documents

Collection (Bộ sưu tập)

Một Collection là một tập hợp các documents cùng loại. Nó giống như một bảng trong Excel hoặc một thư mục chứa nhiều tệp. Ví dụ trong hệ thống Task Management:
  • tasks - Collection chứa tất cả công việc cần làm
  • tasks/{taskId}/comments - SubCollection chứa bình luận của từng task
  • projects - Collection chứa tất cả dự án
  • users - Collection chứa thông tin thành viên team

Document (Tài liệu)

Một Document là một bản ghi duy nhất trong collection, chứa các thông tin chi tiết. Nó giống như một hàng trong Excel. Ví dụ: Một document trong collection tasks có thể là:
ID: task-fix-login-bug
- title: "Sửa lỗi đăng nhập không hoạt động"
- status: "in_progress"
- priority: "high"
- assignee: users/dev-alice
- dueDate: 2024-02-20

Cấu trúc phân cấp

Firestore Database
├── tasks (Collection)
│   ├── task-fix-login-bug (Document)
│   │   ├── title: "Sửa lỗi đăng nhập không hoạt động"
│   │   ├── status: "in_progress"
│   │   ├── priority: "high"
│   │   ├── assignee: users/dev-alice
│   │   └── comments (SubCollection)       ← nằm bên trong task
│   │       ├── comment-001 (Document)
│   │       │   ├── content: "Đã tìm ra nguyên nhân"
│   │       │   ├── author: users/dev-alice
│   │       │   └── createdAt: 2024-01-21T09:00:00Z
│   │       └── comment-002 (Document)
│   │           ├── content: "Fix xong, đang chờ review"
│   │           ├── author: users/dev-alice
│   │           └── createdAt: 2024-01-22T14:00:00Z
│   ├── task-design-dashboard (Document)
│   │   ├── title: "Thiết kế giao diện Dashboard"
│   │   ├── status: "todo"
│   │   ├── priority: "medium"
│   │   └── comments (SubCollection)
│   │       └── ...
│   └── ...
├── projects (Collection)
│   ├── project-mobile-app (Document)
│   │   ├── name: "Ứng dụng Mobile 2024"
│   │   ├── status: "active"
│   │   └── teamSize: 5
│   └── ...
└── users (Collection)
    └── ...

Các Kiểu Dữ liệu (Datatypes)

Firebase Firestore hỗ trợ nhiều kiểu dữ liệu khác nhau. Dưới đây là những loại phổ biến nhất:

1. Text (Văn bản)

Dùng để lưu: Tên, mô tả, email, địa chỉ, hoặc bất kỳ thông tin văn bản nào.
title: "Sửa lỗi đăng nhập không hoạt động"
description: "Người dùng không thể đăng nhập bằng email sau bản cập nhật v2.1"
status: "in_progress"
priority: "high"

2. Number (Số)

Dùng để lưu: Giá tiền, số lượng, tuổi, hoặc bất kỳ con số nào.
estimatedHours: 8
actualHours: 5.5
percentComplete: 70
storyPoints: 3
Số nguyên vs Số thập phân
  • Số nguyên (Integer): 100, 50, 2024
  • Số thập phân (Decimal): 99.99, 4.5, 3.14

3. Boolean (Đúng/Sai)

Dùng để lưu: Tình trạng mở/tắt, có/không, hoặc trạng thái nhị phân.
isCompleted: false
isBlocked: true
isPinned: true
hasAttachments: false

4. Timestamp (Thời gian)

Dùng để lưu: Ngày giờ tạo, ngày giờ cập nhật, hoặc bất kỳ mốc thời gian nào.
createdAt: 2024-01-15T10:30:00Z
updatedAt: 2024-01-20T14:45:30Z
dueDate: 2024-02-20T23:59:59Z
completedAt: null

5. Array (Mảng - Danh sách)

Dùng để lưu: Danh sách các mục, kỹ năng, hoặc thẻ.
tags: ["bug", "authentication", "urgent"]
watchers: ["users/dev-alice", "users/manager-john"]
dependencies: ["task-setup-auth", "task-configure-db"]
checklist: ["Reproduce lỗi", "Viết test case", "Fix code", "Deploy"]

6. Map (Bản đồ - Đối tượng)

Dùng để lưu: Thông tin chi tiết được nhóm lại, giống như một object.
assignee:
  id: "dev-alice"
  name: "Nguyễn Alice"
  avatar: "https://storage.example.com/users/alice.jpg"

timeTracking:
  estimated: 8
  logged: 5.5
  remaining: 2.5

7. Reference (Liên kết)

Dùng để lưu: Tham chiếu đến document khác (để liên kết dữ liệu).
projectId: projects/project-mobile-app
assignee: users/dev-alice
reporter: users/manager-john

8. Null (Không có giá trị)

Dùng để lưu: Khi một trường không có thông tin.
completedAt: null   # Task chưa hoàn thành
assignee: null      # Task chưa được giao cho ai
parentTask: null    # Task không thuộc task cha nào

9. Enum Pattern (Giá trị cố định)

Firestore không có kiểu enum native. Thay vào đó, bạn dùng Text và tự quy ước tập giá trị hợp lệ — việc kiểm tra đúng/sai phải làm ở tầng application code.
# task status — chỉ dùng 4 giá trị này
status: "todo"         # todo | in_progress | done | blocked

# task priority — chỉ dùng 4 giá trị này
priority: "high"       # low | medium | high | critical
Firestore không tự động chặn giá trị sai. Nếu code lưu status: "xyz", Firestore vẫn chấp nhận. Bạn phải validate ở phía app trước khi ghi xuống database.
Best practice: Định nghĩa các giá trị hợp lệ bằng const trong TypeScript để tránh lỗi typo:
TypeScript
const TASK_STATUS = ["todo", "in_progress", "done", "blocked"] as const;
type TaskStatus = typeof TASK_STATUS[number];
// TaskStatus = "todo" | "in_progress" | "done" | "blocked"

const TASK_PRIORITY = ["low", "medium", "high", "critical"] as const;
type TaskPriority = typeof TASK_PRIORITY[number];

Ví dụ Thực tế: Hệ thống Quản lý Dự án (ProjectOS)

Hãy xem cách tổ chức dữ liệu cho một hệ thống quản lý công việc như ProjectOS - nền tảng quản lý dự án cho các team phát triển phần mềm:
Firebase Firestore Database

├── projects (Collection)
│   └── project-webapp-2024 (Document)
│       ├── name: "Website Bán Hàng 2024" (Text)
│       ├── description: "Xây dựng website thương mại điện tử" (Text)
│       ├── status: "in_progress" (Text)
│       ├── startDate: 2024-01-01T00:00:00Z (Timestamp)
│       ├── endDate: 2024-06-30T23:59:59Z (Timestamp)
│       ├── budget: 500000000 (Number - VND)
│       ├── actualCost: 320000000 (Number - chi phí thực tế)
│       ├── manager: users/manager-john (Reference)
│       ├── members: ["users/dev-alice", "users/dev-bob", "users/designer-carol"] (Array)
│       ├── progress: 65 (Number - %)
│       ├── isActive: true (Boolean)
│       ├── createdAt: 2023-12-15T09:00:00Z (Timestamp)
│       └── updatedAt: 2024-01-25T14:30:00Z (Timestamp)

├── tasks (Collection)
│   ├── task-design-homepage (Document)
│   │   ├── taskId: "task-design-homepage" (Text)
│   │   ├── projectId: projects/project-webapp-2024 (Reference)
│   │   ├── title: "Thiết kế giao diện trang chủ" (Text)
│   │   ├── description: "Tạo mockup và design system cho homepage" (Text)
│   │   ├── status: "in_progress" (Text - pending/in_progress/done/blocked)
│   │   ├── priority: "high" (Text - low/medium/high/critical)
│   │   ├── assignee: users/designer-carol (Reference)
│   │   ├── dueDate: 2024-02-14T23:59:59Z (Timestamp)
│   │   ├── estimatedHours: 40 (Number)
│   │   ├── actualHours: 32.5 (Number)
│   │   ├── percentComplete: 85 (Number - %)
│   │   ├── tags: ["design", "frontend", "ui-ux"] (Array)
│   │   ├── dependencies: ["task-setup-design-system"] (Array)
│   │   ├── budget: 50000000 (Number)
│   │   ├── createdAt: 2024-01-10T10:00:00Z (Timestamp)
│   │   └── updatedAt: 2024-01-25T16:45:00Z (Timestamp)
│   │
│   └── task-api-development (Document)
│       ├── taskId: "task-api-development"
│       ├── projectId: projects/project-webapp-2024
│       ├── title: "Phát triển API cho chức năng thanh toán"
│       ├── status: "pending"
│       ├── priority: "critical"
│       ├── assignee: users/dev-alice
│       ├── dueDate: 2024-03-15T23:59:59Z
│       ├── estimatedHours: 80
│       ├── actualHours: 0
│       ├── percentComplete: 0
│       ├── budget: 150000000
│       └── dependencies: ["task-design-homepage"]

├── users (Collection)
│   ├── dev-alice (Document)
│   │   ├── name: "Nguyễn Alice" (Text)
│   │   ├── email: "alice@example.com" (Text)
│   │   ├── role: "developer" (Text - admin/manager/developer/designer)
│   │   ├── avatar: "https://storage.example.com/users/alice.jpg" (Text)
│   │   ├── department: "Engineering" (Text)
│   │   ├── isActive: true (Boolean)
│   │   ├── skills: ["JavaScript", "React", "Node.js"] (Array)
│   │   ├── assignedTasks: ["task-api-development"] (Array)
│   │   ├── totalHoursAllocated: 320 (Number)
│   │   ├── totalHoursUsed: 220.5 (Number)
│   │   ├── createdAt: 2023-08-01T08:00:00Z (Timestamp)
│   │   └── updatedAt: 2024-01-25T09:00:00Z (Timestamp)
│   │
│   └── designer-carol (Document)
│       ├── name: "Carol Designer"
│       ├── email: "carol@example.com"
│       ├── role: "designer"
│       ├── skills: ["Figma", "UI Design", "Prototyping"]
│       ├── assignedTasks: ["task-design-homepage"]
│       └── ...

├── budget (Collection)
│   └── budget-webapp-2024 (Document)
│       ├── projectId: projects/project-webapp-2024 (Reference)
│       ├── totalBudget: 500000000 (Number - VND)
│       ├── allocations: (Map - phân bổ chi phí)
│       │   ├── development: 250000000
│       │   ├── design: 100000000
│       │   ├── testing: 80000000
│       │   └── infrastructure: 50000000
│       ├── expenses: (Array - danh sách chi phí thực tế)
│       │   ├── {date: 2024-01-20, category: "development", amount: 50000000}
│       │   └── {date: 2024-01-22, category: "infrastructure", amount: 15000000}
│       ├── totalSpent: 320000000 (Number)
│       ├── remaining: 180000000 (Number)
│       └── updatedAt: 2024-01-25T16:00:00Z (Timestamp)

├── risks (Collection)
│   └── risk-payment-gateway-delay (Document)
│       ├── projectId: projects/project-webapp-2024 (Reference)
│       ├── title: "Nhà cung cấp thanh toán chậm setup API" (Text)
│       ├── description: "Payment gateway provider có thể chậm cấp credentials" (Text)
│       ├── probability: "high" (Text - low/medium/high)
│       ├── impact: "high" (Text - low/medium/high/critical)
│       ├── severity: 8 (Number - 1-10)
│       ├── status: "active" (Text - active/mitigated/closed)
│       ├── owner: users/manager-john (Reference)
│       ├── mitigation: "Liên hệ provider sớm, có backup plan" (Text)
│       ├── contingency: 50000000 (Number - chi phí dự phòng)
│       ├── affectedTasks: ["task-api-development"] (Array)
│       └── createdAt: 2024-01-15T10:00:00Z (Timestamp)

├── documents (Collection)
│   └── doc-requirements (Document)
│       ├── projectId: projects/project-webapp-2024 (Reference)
│       ├── title: "Tài liệu yêu cầu kỹ thuật" (Text)
│       ├── type: "requirement" (Text)
│       ├── content: "Yêu cầu chi tiết về phạm vi dự án..." (Text)
│       ├── author: users/manager-john (Reference)
│       ├── version: 2 (Number)
│       ├── isPublished: true (Boolean)
│       ├── tags: ["requirements", "scope"] (Array)
│       ├── createdAt: 2024-01-05T10:00:00Z (Timestamp)
│       └── updatedAt: 2024-01-22T14:00:00Z (Timestamp)

├── meetings (Collection)
│   └── meeting-kickoff (Document)
│       ├── projectId: projects/project-webapp-2024 (Reference)
│       ├── title: "Hội họp khởi động dự án" (Text)
│       ├── description: "Tìm hiểu chi tiết, phân chia nhiệm vụ" (Text)
│       ├── scheduledAt: 2024-01-16T09:00:00Z (Timestamp)
│       ├── duration: 120 (Number - phút)
│       ├── organizer: users/manager-john (Reference)
│       ├── attendees: ["users/dev-alice", "users/designer-carol"] (Array)
│       ├── notes: "Những điểm cần lưu ý..." (Text)
│       ├── decisions: ["Sử dụng React 18", "Deploy trên AWS"] (Array)
│       ├── actionItems: (Array)
│       │   ├── {task: "Setup dev environment", owner: "dev-alice", dueDate: 2024-01-17}
│       │   └── {task: "Design mockups", owner: "designer-carol", dueDate: 2024-01-18}
│       ├── isCompleted: true (Boolean)
│       └── createdAt: 2024-01-16T09:00:00Z (Timestamp)

└── reports (Collection)
    └── report-monthly-jan (Document)
        ├── projectId: projects/project-webapp-2024 (Reference)
        ├── period: "2024-01" (Text)
        ├── totalTasks: 25 (Number)
        ├── completedTasks: 15 (Number)
        ├── completionRate: 60 (Number - %)
        ├── progress: 65 (Number - %)
        ├── budget: (Map)
        │   ├── allocated: 500000000
        │   ├── spent: 320000000
        │   └── variance: -180000000 (tiết kiệm)
        ├── risks: 3 (Number - số rủi ro hoạt động)
        ├── blockedTasks: 2 (Number)
        ├── teamPerformance: (Map)
        │   ├── utilization: 80 (Number - %)
        │   └── topPerformers: ["users/dev-alice"]
        └── createdAt: 2024-02-01T10:00:00Z (Timestamp)
ProjectOS Collections:
  • projects - Các dự án trong hệ thống
  • tasks - Công việc hàng ngày của team
  • users - Thành viên team, vai trò, kỹ năng
  • budget - Chi phí dự án, phân bổ ngân sách
  • risks - Các rủi ro tiềm ẩn, mitigation plan
  • documents - Tài liệu, yêu cầu, architecture
  • meetings - Hội họp, ghi chú, quyết định
  • reports - Báo cáo tình trạng, phân tích hiệu suất

Best Practices - Cách tổ chức dữ liệu tốt

1. ✅ Đặt tên rõ ràng

✓ ĐÚNG:
tasks
projects
users
task_comments
project_members

✗ SAI:
t, p, u
data, info
stuff
table1, table2

2. ✅ Dùng ID có ý nghĩa (nếu có thể)

✓ ĐÚNG:
tasks/task-fix-login-bug
tasks/task-design-dashboard
projects/project-mobile-app-2024

✗ SAI:
tasks/abc123xyz
tasks/task1
records/r1

3. ✅ Tổ chức dữ liệu theo logic

Nhóm thông tin liên quan vào cùng một document hoặc sử dụng Map:
✓ ĐÚNG - Dùng Map để nhóm thông tin:
task-fix-login-bug
├── title: "Sửa lỗi đăng nhập"
├── status: "in_progress"
└── timeTracking: (Map)
    ├── estimated: 8
    ├── logged: 5.5
    └── remaining: 2.5

✗ SAI - Tạo quá nhiều field riêng lẻ:
task-fix-login-bug
├── title: "Sửa lỗi đăng nhập"
├── status: "in_progress"
├── timeTracking_estimated: 8
├── timeTracking_logged: 5.5
├── timeTracking_remaining: 2.5

4. ✅ Tránh dữ liệu quá lớn

✓ ĐÚNG - Lưu link hoặc reference:
task-fix-login-bug
├── title: "Sửa lỗi đăng nhập"
├── attachmentUrl: "https://storage.example.com/files/screenshot.png"
└── commentIds: ["comment001", "comment002"]

✗ SAI - Lưu dữ liệu quá lớn trong document:
task-fix-login-bug
├── title: "Sửa lỗi đăng nhập"
├── attachmentFile: [binary data - 10MB]
└── allComments: [toàn bộ nội dung 500 comments...]

5. ✅ Dùng Timestamp để theo dõi thay đổi

document
├── createdAt: 2024-01-01T10:00:00Z  // Khi tạo
├── updatedAt: 2024-01-20T14:30:00Z  // Lần cập nhật gần nhất
└── deletedAt: null  // Khi xóa (nếu dùng soft delete)

Chiến lược Tổ chức Data - Flat vs Nested

Flat Structure (Dẹt)

Mỗi loại thông tin là một collection riêng:
Collections:
- users
- user_addresses
- user_preferences
Ưu điểm: Dễ quản lý, tìm kiếm nhanh, tiết kiệm không gian Nhược điểm: Cần nhiều query hơn để lấy toàn bộ thông tin

Nested Structure (Lồng nhau)

Thông tin được lồng trong document:
- users
  └── user123 (Document)
      ├── name
      ├── email
      └── addresses (SubCollection)
          ├── address001
          └── address002
Ưu điểm: Thông tin liên quan được nhóm lại gọn gàng Nhược điểm: Có thể phức tạp hơn khi truy vấn
Lựa chọn nào tốt hơn? - Thường dùng Flat Structure cho dữ liệu đơn giản, và Nested Structure khi bạn có dữ liệu phức tạp hoặc có mối quan hệ mạnh mẽ giữa các thông tin.

Thao tác với Dữ liệu - Những điều cần biết

Thêm (Create)

Collection: tasks
Document ID: task-fix-login-bug
Dữ liệu:
{
  title: "Sửa lỗi đăng nhập không hoạt động",
  status: "todo",
  priority: "high",
  projectId: projects/project-mobile-app,
  assignee: users/dev-alice,
  dueDate: 2024-02-20T23:59:59Z,
  createdAt: 2024-01-20T10:00:00Z
}

Đọc (Read)

Lấy thông tin task-fix-login-bug:
→ Firestore sẽ trả về:
{
  title: "Sửa lỗi đăng nhập không hoạt động",
  status: "todo",
  priority: "high",
  assignee: users/dev-alice,
  dueDate: 2024-02-20T23:59:59Z
}

Cập nhật (Update)

Cập nhật task-fix-login-bug:
Thay đổi status từ "todo" → "in_progress"
Thêm actualHours: 5.5
updatedAt → 2024-01-25T14:00:00Z

Xóa (Delete)

Xóa document task-fix-login-bug hoàn toàn
hoặc đánh dấu isArchived: true để soft delete (giữ lịch sử)

Chi phí lưu trữ - Cần biết

Firebase Firestore tính phí dựa trên:
Thao tácChi phí
Đọc (Read)~$0.06 per 100,000 reads
Ghi (Write)~$0.18 per 100,000 writes
Xóa (Delete)~$0.02 per 100,000 deletes
Lưu trữ~$0.18 per GB/month
Mẹo tiết kiệm:
  • Nhóm các cập nhật nhỏ lại để giảm số lần ghi
  • Xóa dữ liệu cũ không cần dùng
  • Dùng indexing thông minh để tối ưu query
  • Tránh đọc toàn bộ documents khi chỉ cần một vài field

Tóm lược - Checklist thiết kế Dữ liệu

1

Xác định Collections

Quyết định những loại dữ liệu nào bạn cần (users, products, orders, …)
2

Chọn Datatypes phù hợp

Xác định từng field sẽ là Text, Number, Boolean, Array, Map, …
3

Đặt tên rõ ràng

Sử dụng tên tiếng Anh, snake_case hoặc camelCase, tránh viết tắt
4

Thiết kế ID Document

Quyết định ID tự động hay tự đặt (ví dụ: user_john_doe)
5

Chọn cấu trúc phù hợp

Dùng Flat hoặc Nested tùy nhu cầu
6

Thêm Timestamps

Luôn thêm createdAt và updatedAt để theo dõi
7

Kiểm tra quan hệ

Nếu cần liên kết dữ liệu, dùng Reference hoặc Array of IDs
8

Test với Dữ liệu thực

Thử tạo, cập nhật, xóa dữ liệu để chắc chắn cấu trúc hợp lý

Các câu hỏi thường gặp

Firebase Firestore lưu trữ dữ liệu trên máy chủ của Google với bản sao lưu tự động. Dữ liệu rất an toàn, nhưng bạn nên có chiến lược sao lưu định kỳ cho dữ liệu quan trọng.
Có, bạn có thể thay đổi hoặc thêm field bất kỳ lúc nào. Ví dụ: nếu lúc đầu age là Number, sau đó bạn có thể thêm dateOfBirth mà không ảnh hưởng đến dữ liệu cũ.
Có, mỗi document không được vượt quá 1MB. Nếu cần lưu dữ liệu lớn hơn (như ảnh), hãy dùng Firebase Storage và lưu link trong Firestore.
Firestore cho phép bạn tìm kiếm dựa trên các điều kiện, ví dụ:
  • Tất cả tasks có status: "in_progress" — xem việc đang làm dở
  • Tất cả tasks có assignee: users/dev-alice — việc của một người cụ thể
  • Tất cả tasks có dueDate trước ngày hôm nay — công việc đã quá hạn
  • Tất cả tasks thuộc projectId: projects/project-mobile-app — việc trong một dự án
Firestore tốt hơn nếu:
  • Bạn cần ứng dụng real-time
  • Dữ liệu không cấu trúc chặt chẽ
  • Ứng dụng mobile cần hoạt động offline
SQL Database tốt hơn nếu:
  • Dữ liệu có quan hệ phức tạp (nhiều bảng, join)
  • Bạn cần transaction phức tạp
  • Dữ liệu cấu trúc rất chặt chẽ

Bước tiếp theo

Sau khi hiểu rõ về datatype và cách tổ chức dữ liệu, bạn có thể:
  1. Thiết lập Firebase Project
  2. Thiết kế schema dữ liệu cho ứng dụng của mình
  3. Tạo collections và documents đầu tiên
  4. Bắt đầu lưu trữ và truy vấn dữ liệu thực

Ghi chú: Hướng dẫn này dành cho người không có background lập trình. Nếu bạn là developer, hãy tham khảo tài liệu chính thức của Firebase để biết chi tiết kỹ thuật hơn.