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: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ác Chi 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
Xác định Collections
Quyết định những loại dữ liệu nào bạn cần (users, products, orders, …)
Chọn Datatypes phù hợp
Xác định từng field sẽ là Text, Number, Boolean, Array, Map, …
Đặ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
Thiết kế ID Document
Quyết định ID tự động hay tự đặt (ví dụ: user_john_doe)
Chọn cấu trúc phù hợp
Dùng Flat hoặc Nested tùy nhu cầu
Thêm Timestamps
Luôn thêm createdAt và updatedAt để theo dõi
Kiểm tra quan hệ
Nếu cần liên kết dữ liệu, dùng Reference hoặc Array of IDs
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
Dữ liệu có thể mất không?
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.
Tôi có thể thay đổi kiểu dữ liệu sau khi tạo khô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ũ.
Document có kích thước tối đa không?
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.
Làm sao để tìm kiếm dữ liệu?
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
Tôi nên dùng Firestore hay SQL Database?
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ể:
Thiết lập Firebase Project
Thiết kế schema dữ liệu cho ứng dụng của mình
Tạo collections và documents đầu tiên
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.