Bạn đang xây dựng một ứng dụng. Người dùng cần đăng nhập. Dữ liệu cần đồng bộ hóa theo thời gian thực. Các tệp cần được lưu trữ. Bạn có thể thiết lập máy chủ, cấu hình cơ sở dữ liệu và quản lý hạ tầng trong nhiều tuần. Hoặc bạn có thể sử dụng Firebase.
Firebase cung cấp sức mạnh cho hơn 1,5 triệu ứng dụng bao gồm The New York Times, Duolingo và Alibaba. Các nhà phát triển chọn nó vì nó loại bỏ sự phức tạp của phần phụ trợ (backend). Bạn tập trung vào các tính năng, không phải bảo trì máy chủ. Nhưng API Firebase có những điểm khác biệt. Luồng xác thực làm người mới bối rối. Quy tắc cơ sở dữ liệu làm các nhà phát triển có kinh nghiệm gặp khó khăn. Cloud Functions có vẻ kỳ diệu cho đến khi bạn hiểu các kích hoạt của chúng.
Tôi đã tích hợp Firebase vào các ứng dụng sản xuất phục vụ hàng triệu người dùng. Tôi đã mắc mọi lỗi có thể: làm lộ khóa tài khoản dịch vụ, viết các truy vấn kém hiệu quả, triển khai các hàm bị lỗi. Hướng dẫn này chắt lọc những bài học đó.
Bạn sẽ học về xác thực, các thao tác cơ sở dữ liệu, Cloud Functions và lưu trữ. Bạn sẽ thấy mã hoạt động thực tế, không chỉ lý thuyết. Bạn sẽ tránh được những cạm bẫy gây ra các vấn đề nghiêm trọng trong sản xuất.
nút
API Firebase là gì và tại sao nó lại quan trọng?
Firebase không phải là một API duy nhất. Đó là một bộ các dịch vụ backend được truy cập thông qua các SDK hợp nhất và các điểm cuối REST.
Các Dịch vụ Firebase Cốt lõi
| Dịch vụ | Mục đích | Loại API |
|---|---|---|
| Xác thực | Đăng nhập người dùng và định danh | SDK + REST |
| Cơ sở dữ liệu Firestore | Cơ sở dữ liệu tài liệu NoSQL | SDK + REST |
| Cơ sở dữ liệu Thời gian thực | Đồng bộ hóa JSON thời gian thực | SDK + REST |
| Cloud Storage | Lưu trữ tệp và CDN | SDK + REST |
| Cloud Functions | Tính toán phi máy chủ (Serverless compute) | CLI triển khai |
| Hosting | Lưu trữ web tĩnh | CLI triển khai |
| Cloud Messaging | Thông báo đẩy | API HTTP v1 |
Khi nào nên sử dụng Firebase
Firebase giải quyết tốt các vấn đề cụ thể:
Sử dụng Firebase khi:
- Bạn cần đồng bộ hóa thời gian thực (trò chuyện, cộng tác, cập nhật trực tiếp)
- Bạn muốn kiến trúc phi máy chủ (không cần quản lý hạ tầng)
- Bạn đang xây dựng ứng dụng di động hoặc web (SDK xử lý sự khác biệt giữa các nền tảng)
- Bạn cần hỗ trợ ngoại tuyến (SDK tự động lưu trữ dữ liệu)
- Bạn muốn xác thực tích hợp sẵn (đăng nhập bằng Google, Apple, email, số điện thoại)
Không sử dụng Firebase khi:
- Bạn cần các truy vấn quan hệ phức tạp (hãy dùng PostgreSQL)
- Bạn có yêu cầu nghiêm ngặt về vị trí dữ liệu (các khu vực của Firebase bị hạn chế)
- Bạn cần đầy đủ khả năng SQL (Firestore có những hạn chế về truy vấn)
- Chi phí ở quy mô lớn quan trọng hơn tốc độ phát triển (tự lưu trữ rẻ hơn)
Kiến trúc API Firebase
Firebase sử dụng một cách tiếp cận lai:
┌─────────────────────────────────────────────────────────┐
│ Ứng dụng của bạn │
├─────────────────────────────────────────────────────────┤
│ Firebase SDK (Client) │
│ - Tự động xử lý mã thông báo xác thực │
│ - Quản lý bộ nhớ đệm ngoại tuyến │
│ - Trình lắng nghe thời gian thực │
└─────────────────────────────────────────────────────────┘
│
│ HTTPS + WebSocket
▼
┌─────────────────────────────────────────────────────────┐
│ Backend Firebase │
├──────────────┬──────────────┬──────────────┬────────────┤
│ Dịch vụ │ Cơ sở dữ liệu │ Dịch vụ │ Hàm │
│ Xác thực │ Firestore │ Lưu trữ │ Thời gian chạy │
└──────────────┴──────────────┴──────────────┴────────────┘
Các SDK client trừu tượng hóa lớp HTTP. Về bản chất, mọi thao tác đều được dịch thành các lệnh gọi API REST với xác thực JWT.
Xác thực Firebase: Thiết lập Hoàn chỉnh
Xác thực là tích hợp Firebase đầu tiên của bạn. Nếu làm sai bước này, mọi thứ khác sẽ thất bại.
Bước 1: Tạo dự án Firebase
- Truy cập Firebase Console

Nhấp vào "Thêm dự án" và Nhập tên dự án (không có khoảng trắng)

Bật Google Analytics (tùy chọn nhưng được khuyến nghị)

Nhấp vào "Tạo dự án"

Chờ 30 giây để hoàn tất việc cung cấp. Bạn sẽ thấy bảng điều khiển dự án.
Bước 2: Đăng ký ứng dụng của bạn
Đối với ứng dụng Web:
// Trong Firebase Console > Cài đặt dự án > Tổng quan
// Nhấp vào "Thêm ứng dụng" > biểu tượng Web
// Đăng ký ứng dụng web
const firebaseConfig = {
apiKey: "AIzaSyDxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "your-app.firebaseapp.com",
projectId: "your-app",
storageBucket: "your-app.appspot.com",
messagingSenderId: "123456789012",
appId: "1:123456789012:web:abc123def456"
};
// Khởi tạo Firebase
import { initializeApp } from 'firebase/app';
const app = initializeApp(firebaseConfig);
Đối với ứng dụng iOS:
Tải xuống GoogleService-Info.plist và thêm vào dự án Xcode. Đảm bảo "Target Membership" bao gồm ứng dụng của bạn.
Đối với ứng dụng Android:
Tải xuống google-services.json và đặt vào thư mục app/. Thêm vào build.gradle:
// build.gradle cấp dự án
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.4.0'
}
}
// build.gradle cấp ứng dụng
plugins {
id 'com.google.gms.google-services'
}
Bước 3: Bật các phương thức xác thực
Trong Firebase Console > Xác thực > Phương thức đăng nhập:
- Email/Mật khẩu: Bật để đăng ký truyền thống
- Google: Thêm dấu vân tay chứng chỉ SHA-1 của bạn (Android) hoặc ID gói (iOS)
- Apple: Bắt buộc đối với ứng dụng iOS nếu bạn bật bất kỳ đăng nhập xã hội nào
- Điện thoại: Bật để xác thực bằng SMS (yêu cầu thanh toán)
Bước 4: Triển khai luồng xác thực
Đăng ký bằng Email/Mật khẩu:
import {
createUserWithEmailAndPassword,
getAuth,
updateProfile
} from 'firebase/auth';
const auth = getAuth(app);
async function signUp(email, password, displayName) {
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// Đặt tên hiển thị
await updateProfile(userCredential.user, {
displayName: displayName
});
console.log('Người dùng được tạo:', userCredential.user.uid);
return userCredential.user;
} catch (error) {
// Xử lý các mã lỗi cụ thể
switch (error.code) {
case 'auth/email-already-in-use':
throw new Error('Email này đã được sử dụng');
case 'auth/weak-password':
throw new Error('Mật khẩu phải có ít nhất 6 ký tự');
case 'auth/invalid-email':
throw new Error('Địa chỉ email không hợp lệ');
default:
throw new Error('Đăng ký thất bại: ' + error.message);
}
}
}
Đăng nhập bằng Email/Mật khẩu:
import {
signInWithEmailAndPassword,
signOut
} from 'firebase/auth';
async function signIn(email, password) {
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const user = userCredential.user;
// Lấy mã thông báo ID cho các lệnh gọi API
const idToken = await user.getIdToken();
console.log('Mã thông báo xác thực:', idToken);
return user;
} catch (error) {
switch (error.code) {
case 'auth/user-not-found':
throw new Error('Không có tài khoản nào với email này');
case 'auth/wrong-password':
throw new Error('Mật khẩu không chính xác');
case 'auth/too-many-requests':
throw new Error('Quá nhiều lần thử. Vui lòng thử lại sau');
default:
throw new Error('Đăng nhập thất bại');
}
}
}
async function logOut() {
await signOut(auth);
console.log('Người dùng đã đăng xuất');
}
Đăng nhập bằng Google (Web):
import {
GoogleAuthProvider,
signInWithPopup
} from 'firebase/auth';
async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
// Yêu cầu các phạm vi bổ sung
provider.addScope('email');
provider.addScope('profile');
try {
const result = await signInWithPopup(auth, provider);
const user = result.user;
// Truy cập mã thông báo OAuth của Google
const credential = GoogleAuthProvider.credentialFromResult(result);
const googleAccessToken = credential.accessToken;
return user;
} catch (error) {
if (error.code === 'auth/popup-closed-by-user') {
throw new Error('Đăng nhập đã bị hủy');
}
throw new Error('Đăng nhập bằng Google thất bại');
}
}
Bước 5: Bảo vệ các tuyến đường với trạng thái xác thực
import { onAuthStateChanged } from 'firebase/auth';
// Đăng ký lắng nghe thay đổi trạng thái xác thực
onAuthStateChanged(auth, (user) => {
if (user) {
// Người dùng đã đăng nhập
console.log('Người dùng:', user.email);
// Chuyển hướng đến bảng điều khiển
window.location.href = '/dashboard';
} else {
// Người dùng đã đăng xuất
console.log('Không có người dùng nào');
// Chuyển hướng đến trang đăng nhập
window.location.href = '/login';
}
});
Các lỗi xác thực thường gặp
Lỗi 1: Không xử lý làm mới mã thông báo
Firebase SDK tự động làm mới mã thông báo. Nhưng nếu bạn lưu trữ mã thông báo phía máy chủ, chúng sẽ hết hạn sau 1 giờ. Luôn xác minh mã thông báo trên mỗi yêu cầu hoặc triển khai logic làm mới.
Lỗi 2: Để lộ thông tin đăng nhập quản trị viên trong mã client
Không bao giờ sử dụng khóa tài khoản dịch vụ trong các ứng dụng client. Tài khoản dịch vụ bỏ qua các quy tắc bảo mật. Chỉ sử dụng chúng trong môi trường máy chủ đáng tin cậy.
Lỗi 3: Bỏ qua xác minh email
import { sendEmailVerification } from 'firebase/auth';
async function sendVerificationEmail(user) {
await sendEmailVerification(user);
console.log('Email xác minh đã được gửi');
}
// Kiểm tra trạng thái xác minh
if (!auth.currentUser.emailVerified) {
console.log('Email chưa được xác minh');
// Hạn chế quyền truy cập
}
Cơ sở dữ liệu Firestore: Các thao tác và Truy vấn
Firestore là cơ sở dữ liệu NoSQL của Firebase. Các tài liệu được tổ chức thành các bộ sưu tập. Các truy vấn tự động mở rộng.
Cấu trúc dữ liệu
your-project (gốc)
└── users (bộ sưu tập)
├── userId123 (tài liệu)
│ ├── name: "John"
│ ├── email: "john@example.com"
│ └── posts (bộ sưu tập con)
│ ├── postId1 (tài liệu)
│ └── postId2 (tài liệu)
└── userId456 (tài liệu)
Khởi tạo Firestore
import { getFirestore } from 'firebase/firestore';
const db = getFirestore(app);
Tạo tài liệu
import {
collection,
addDoc,
setDoc,
doc
} from 'firebase/firestore';
// Tùy chọn 1: ID tự động tạo
async function createUser(userData) {
const docRef = await addDoc(collection(db, 'users'), userData);
console.log('Tài liệu được ghi với ID:', docRef.id);
return docRef.id;
}
// Tùy chọn 2: ID tùy chỉnh
async function createUserWithId(userId, userData) {
await setDoc(doc(db, 'users', userId), userData);
console.log('Tài liệu được ghi với ID tùy chỉnh:', userId);
}
// Cách sử dụng
const userId = await createUser({
name: 'Alice',
email: 'alice@example.com',
createdAt: new Date(),
role: 'user'
});
Đọc tài liệu
import {
getDoc,
getDocs,
query,
where,
orderBy,
limit
} from 'firebase/firestore';
// Lấy một tài liệu
async function getUser(userId) {
const docRef = doc(db, 'users', userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
return docSnap.data();
} else {
throw new Error('Không tìm thấy người dùng');
}
}
// Truy vấn với bộ lọc
async function getUsersByRole(role) {
const q = query(
collection(db, 'users'),
where('role', '==', role),
orderBy('createdAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});
return users;
}
// Cách sử dụng
const adminUsers = await getUsersByRole('admin');
console.log('Người dùng quản trị:', adminUsers);
Cập nhật tài liệu
import {
updateDoc,
increment,
arrayUnion,
arrayRemove
} from 'firebase/firestore';
async function updateUser(userId, updates) {
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, updates);
}
// Các thao tác nguyên tử
await updateUser('userId123', {
loginCount: increment(1),
tags: arrayUnion('premium', 'beta-tester'),
lastLogin: new Date()
});
// Xóa khỏi mảng
await updateUser('userId123', {
tags: arrayRemove('beta-tester')
});
Xóa tài liệu
import { deleteDoc } from 'firebase/firestore';
async function deleteUser(userId) {
await deleteDoc(doc(db, 'users', userId));
console.log('Người dùng đã bị xóa');
}
Trình lắng nghe thời gian thực
import { onSnapshot } from 'firebase/firestore';
// Lắng nghe một tài liệu
const unsubscribe = onSnapshot(
doc(db, 'users', userId),
(doc) => {
console.log('Người dùng đã được cập nhật:', doc.data());
},
(error) => {
console.error('Lỗi lắng nghe:', error);
}
);
// Lắng nghe kết quả truy vấn
const q = query(collection(db, 'posts'), where('published', '==', true));
const unsubscribeQuery = onSnapshot(q, (snapshot) => {
const posts = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
console.log('Bài viết đã xuất bản:', posts);
});
// Ngừng lắng nghe
unsubscribe();
unsubscribeQuery();
Quy tắc bảo mật Firestore
Nếu không có quy tắc phù hợp, bất kỳ ai cũng có thể đọc dữ liệu của bạn. Đặt quy tắc trong Firebase Console > Firestore > Quy tắc:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Hàm trợ giúp
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
// Bộ sưu tập người dùng
match /users/{userId} {
allow read: if isAuthenticated();
allow create: if isAuthenticated() && isOwner(userId);
allow update, delete: if isOwner(userId);
}
// Bộ sưu tập bài viết
match /posts/{postId} {
allow read: if true; // Đọc công khai
allow create: if isAuthenticated();
allow update, delete: if resource.data.authorId == request.auth.uid;
}
// Bộ sưu tập con riêng tư
match /users/{userId}/private/{document} {
allow read, write: if isOwner(userId);
}
}
}
Hạn chế về truy vấn
Firestore có những ràng buộc:
- Không có truy vấn OR (sử dụng
invới mảng hoặc nhiều truy vấn) - Không có tìm kiếm ký tự đại diện (sử dụng Algolia hoặc Meilisearch cho tìm kiếm toàn văn bản)
- Các truy vấn kết hợp cần chỉ mục (Firestore tự động tạo chỉ mục một trường)
- Giới hạn 30 disjunctions trong các truy vấn
in
Cách khắc phục cho các truy vấn OR:
// Thay vì: where('status', '==', 'active') OR where('status', '==', 'pending')
const activeQuery = query(
collection(db, 'tasks'),
where('status', '==', 'active')
);
const pendingQuery = query(
collection(db, 'tasks'),
where('status', '==', 'pending')
);
const [activeSnap, pendingSnap] = await Promise.all([
getDocs(activeQuery),
getDocs(pendingQuery)
]);
// Hợp nhất kết quả trong client
Cloud Functions: Logic Backend phi máy chủ
Cloud Functions chạy mã backend mà không cần quản lý máy chủ. Kích hoạt theo thay đổi cơ sở dữ liệu, yêu cầu HTTP hoặc các sự kiện đã lên lịch.
Thiết lập
# Cài đặt Firebase CLI
npm install -g firebase-tools
# Đăng nhập
firebase login
# Khởi tạo functions trong dự án của bạn
firebase init functions
# Chọn: JavaScript, ESLint yes, Express.js no
Hàm HTTP (Điểm cuối API)
// functions/index.js
const { onRequest } = require('firebase-functions/v2/https');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
// Điểm cuối công khai
exports.getPublicData = onRequest(async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
try {
const snapshot = await db.collection('public').get();
const data = snapshot.docs.map(doc => doc.data());
res.json({ success: true, data });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Điểm cuối được bảo vệ (xác minh mã thông báo xác thực)
exports.getUserProfile = onRequest(async (req, res) => {
res.set('Access-Control-Allow-Origin', '*');
// Lấy mã thông báo từ tiêu đề Authorization
const authHeader = req.headers.authorization || '';
const token = authHeader.split('Bearer ')[1];
if (!token) {
return res.status(401).json({ error: 'Không được ủy quyền' });
}
try {
// Xác minh mã thông báo
const decodedToken = await admin.auth().verifyIdToken(token);
const userId = decodedToken.uid;
// Lấy dữ liệu người dùng
const userDoc = await db.collection('users').doc(userId).get();
if (!userDoc.exists) {
return res.status(404).json({ error: 'Không tìm thấy người dùng' });
}
res.json({
success: true,
data: { id: userId, ...userDoc.data() }
});
} catch (error) {
res.status(401).json({ error: 'Mã thông báo không hợp lệ' });
}
});
Triển khai:
firebase deploy --only functions:getUserProfile
Gọi từ client:
async function getUserProfile(token) {
const response = await fetch(
'https://us-central1-your-app.cloudfunctions.net/getUserProfile',
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const data = await response.json();
return data;
}
Kích hoạt cơ sở dữ liệu
const { onDocumentWritten } = require('firebase-functions/v2/firestore');
// Kích hoạt khi tài liệu người dùng thay đổi
exports.onUserUpdate = onDocumentWritten(
'users/{userId}',
async (event) => {
const userId = event.params.userId;
const before = event.data?.before?.data();
const after = event.data?.after?.data();
// Kiểm tra nếu email thay đổi
if (before?.email !== after?.email) {
console.log(`Email người dùng ${userId} đã thay đổi: ${before?.email} → ${after?.email}`);
// Gửi email thông báo
await admin.auth().getUser(userId);
// Thêm logic email của bạn ở đây
}
}
);
// Kích hoạt khi tạo bài viết mới
exports.onNewPost = onDocumentWritten(
'posts/{postId}',
async (event) => {
const post = event.data?.after?.data();
if (!post) return; // Tài liệu đã bị xóa
// Kiểm tra xem đây có phải là tài liệu mới không
if (!event.data?.before?.exists) {
console.log('Bài viết mới được tạo:', post.title);
// Thông báo cho người theo dõi
const followersSnap = await admin.firestore()
.collection('users')
.where('following', 'array-contains', post.authorId)
.get();
const notifications = followersSnap.docs.map(doc => ({
userId: doc.id,
postId: event.params.postId,
type: 'new_post',
createdAt: admin.firestore.FieldValue.serverTimestamp()
}));
const batch = admin.firestore().batch();
notifications.forEach(notif => {
const ref = admin.firestore().collection('notifications').doc();
batch.set(ref, notif);
});
await batch.commit();
}
}
);
Hàm được lên lịch (Cron Jobs)
const { onSchedule } = require('firebase-functions/v2/scheduler');
// Chạy mỗi ngày vào nửa đêm UTC
exports.dailyCleanup = onSchedule('every 24 hours', async (event) => {
console.log('Đang chạy dọn dẹp hàng ngày');
// Xóa các thông báo cũ (hơn 30 ngày)
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const oldNotifs = await admin.firestore()
.collection('notifications')
.where('createdAt', '<', thirtyDaysAgo)
.get();
const batch = admin.firestore().batch();
oldNotifs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
console.log(`Đã xóa ${oldNotifs.size} thông báo cũ`);
});
Cấu hình môi trường
# Đặt biến môi trường
firebase functions:config:set \
stripe.secret="sk_test_xxx" \
email.api_key="key_xxx"
# Truy cập trong các hàm
const config = require('firebase-functions/config');
const stripe = require('stripe')(config.stripe.secret);
Cloud Storage: Tải lên và Quản lý tệp
Lưu trữ các tệp tải lên của người dùng, hình ảnh và tệp với phân phối CDN tự động.
Thiết lập Quy tắc Storage
// Firebase Console > Storage > Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Thư mục tải lên của người dùng
match /users/{userId}/{allPaths=**} {
allow read: if true; // Đọc công khai
allow write: if request.auth.uid == userId;
allow delete: if request.auth.uid == userId;
}
// Tài sản công khai
match /public/{allPaths=**} {
allow read: if true;
allow write: if false; // Chỉ quản trị viên thông qua Firebase Console
}
}
}
Tải lên tệp (Client)
import {
getStorage,
ref,
uploadBytesResumable,
getDownloadURL
} from 'firebase/storage';
const storage = getStorage(app);
async function uploadProfileImage(userId, file) {
// Tạo tham chiếu lưu trữ
const storageRef = ref(storage, `users/${userId}/profile/${file.name}`);
// Tải lên tệp
const uploadTask = uploadBytesResumable(storageRef, file);
return new Promise((resolve, reject) => {
uploadTask.on(
'state_changed',
(snapshot) => {
// Theo dõi tiến độ
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(`Tải lên: ${progress.toFixed(0)}%`);
},
(error) => {
// Xử lý lỗi
switch (error.code) {
case 'storage/unauthorized':
reject(new Error('Bạn không có quyền'));
break;
case 'storage/canceled':
reject(new Error('Tải lên đã bị hủy'));
break;
default:
reject(new Error('Tải lên thất bại'));
}
},
async () => {
// Tải lên hoàn tất
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
console.log('Tệp có sẵn tại:', downloadURL);
resolve(downloadURL);
}
);
});
}
// Cách sử dụng
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
if (file) {
const imageUrl = await uploadProfileImage(auth.currentUser.uid, file);
// Lưu URL vào Firestore
await updateDoc(doc(db, 'users', auth.currentUser.uid), {
profileImage: imageUrl
});
}
Tải xuống tệp
import { getDownloadURL } from 'firebase/storage';
async function getProfileImage(userId) {
const imageRef = ref(storage, `users/${userId}/profile/avatar.png`);
try {
const url = await getDownloadURL(imageRef);
return url;
} catch (error) {
if (error.code === 'storage/object-not-found') {
return null; // Không có hình ảnh hồ sơ
}
throw error;
}
}
Xóa tệp
import { deleteObject } from 'firebase/storage';
async function deleteProfileImage(userId) {
const imageRef = ref(storage, `users/${userId}/profile/avatar.png`);
await deleteObject(imageRef);
console.log('Ảnh đại diện đã bị xóa');
}
Kiểm thử API Firebase với Apidog
Firebase cung cấp các API REST cho tất cả các dịch vụ. Kiểm thử chúng trực tiếp giúp gỡ lỗi các vấn đề và hiểu các yêu cầu cơ bản.
Nhập API REST Firebase
- Mở Apidog
- Tạo dự án mới: “Firebase API”
- Nhập thông số kỹ thuật OpenAPI từ tài liệu Firebase
- Hoặc thêm điểm cuối theo cách thủ công:
Điểm cuối REST Firestore:
POST https://firestore.googleapis.com/v1/projects/{projectId}/databases/(default)/documents
Authorization: Bearer {oauth2_token}
Content-Type: application/json
{
"fields": {
"name": { "stringValue": "John" },
"email": { "stringValue": "john@example.com" },
"age": { "integerValue": 30 }
}
}
Điểm cuối xác thực:
POST https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={api_key}
Content-Type: application/json
{
"email": "user@example.com",
"password": "secret123",
"returnSecureToken": true
}
Kiểm thử luồng xác thực
- Tạo yêu cầu: “Đăng nhập”
- Đặt phương thức: POST
- Thêm email/mật khẩu vào phần thân
- Lưu mã thông báo phản hồi dưới dạng biến môi trường
- Sử dụng
{{token}}trong các yêu cầu tiếp theo
Gỡ lỗi quy tắc bảo mật
Sử dụng Firebase Emulator Suite để kiểm thử cục bộ:
# Khởi động emulator
firebase emulators:start
# Kiểm thử với Firestore cục bộ
# http://localhost:8080
Các Thực hành Tốt nhất trong Sản xuất
1. Triển khai Xử lý lỗi thích hợp
// Logic thử lại cho các lỗi tạm thời
async function firestoreWithRetry(operation, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (
error.code === 'unavailable' ||
error.code === 'deadline-exceeded'
) {
const delay = Math.pow(2, i) * 1000; // Quay lại theo cấp số nhân
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
2. Tối ưu hóa hiệu suất truy vấn
Thêm chỉ mục tổng hợp cho các truy vấn đa trường:
// Truy vấn này cần một chỉ mục tổng hợp
const q = query(
collection(db, 'posts'),
where('category', '==', 'tech'),
where('views', '>', 1000),
orderBy('views', 'desc')
);
Firestore nhắc bạn tạo chỉ mục với một liên kết trực tiếp khi bạn chạy truy vấn này.
3. Các thao tác hàng loạt
import { writeBatch } from 'firebase/firestore';
async function bulkUpdate(userIds, updates) {
const batch = writeBatch(db);
userIds.forEach(id => {
const ref = doc(db, 'users', id);
batch.update(ref, updates);
});
await batch.commit();
console.log(`Đã cập nhật ${userIds.length} người dùng`);
}
// Tối đa 500 thao tác mỗi batch
4. Giám sát chi phí
Giá Firebase:
| Dịch vụ | Gói miễn phí | Trả phí |
|---|---|---|
| Firestore | 50K lượt đọc/ngày | $0.036/100K lượt đọc |
| Storage | 5GB | $0.023/GB |
| Functions | 2M lượt gọi | $0.40/1M |
| Auth | 10K/tháng | $0.0055/100K |
Đặt cảnh báo ngân sách trong Google Cloud Console.
5. Bảo mật tài khoản dịch vụ
// SAI: Không bao giờ làm điều này trong mã client
admin.initializeApp({
credential: admin.credential.cert(require('./serviceAccountKey.json'))
});
// ĐÚNG: Chỉ sử dụng trong môi trường máy chủ
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
6. Xử lý các kịch bản ngoại tuyến
// Bật tính năng bền vững ngoại tuyến (web)
import { enableMultiTabIndexedDbPersistence } from 'firebase/firestore';
enableMultiTabIndexedDbPersistence(db)
.catch((err) => {
if (err.code === 'failed-precondition') {
// Nhiều tab đang mở
} else if (err.code === 'unimplemented') {
// Trình duyệt không hỗ trợ
}
});
// Lắng nghe kết nối
import { onSnapshot } from 'firebase/firestore';
onSnapshot(doc(db, 'status', 'online'), (doc) => {
if (!doc.exists()) {
console.log('Bạn đang ngoại tuyến');
// Hiển thị giao diện ngoại tuyến
}
});
Các vấn đề và Giải pháp API Firebase Thường gặp
Vấn đề 1: Lỗi từ chối quyền
Triệu chứng: Lỗi: 7 PERMISSION_DENIED
Nguyên nhân: Các quy tắc bảo mật chặn thao tác
Cách khắc phục:
- Kiểm tra quy tắc trong Firebase Console
- Xác minh
request.auth.uidkhớp với người dùng mong muốn - Kiểm thử quy tắc bằng Rules Playground
Vấn đề 2: Mã thông báo hết hạn
Triệu chứng: Lỗi: Mã thông báo ID đã hết hạn
Cách khắc phục:
// Buộc làm mới mã thông báo
const user = auth.currentUser;
if (user) {
await user.getIdToken(true); // Buộc làm mới
}
Vấn đề 3: Độ trễ khởi động nguội
Triệu chứng: Cloud Functions mất 2-5 giây trong lần gọi đầu tiên
Cách khắc phục:
// Giữ cho các hàm luôn "ấm" bằng các ping theo lịch trình
exports.keepWarm = onSchedule('every 60 seconds', async () => {
await fetch('https://your-function.cloudfunctions.net/health');
});
Vấn đề 4: Truy vấn trả về kết quả trống
Triệu chứng: Truy vấn lẽ ra phải trả về dữ liệu nhưng lại trả về mảng trống
Nguyên nhân: Thiếu chỉ mục hoặc thứ tự trường sai
Cách khắc phục: Kiểm tra Firestore Console > Indexes để tìm các chỉ mục tổng hợp cần thiết.
Các trường hợp sử dụng thực tế
Ứng dụng Fintech: Cập nhật giao dịch thời gian thực
Một công ty khởi nghiệp thanh toán đã sử dụng Firebase Firestore để xây dựng các thông báo giao dịch thời gian thực. Khi một khoản thanh toán được xử lý, Cloud Functions kích hoạt cập nhật đến tất cả các bảng điều khiển quản trị viên được kết nối trong vòng 200ms. Kết quả: Giảm 40% số lượng yêu cầu hỗ trợ về các giao dịch "đang chờ xử lý".
Thương mại điện tử: Đồng bộ hóa kho hàng
Một nhà bán lẻ trực tuyến đồng bộ hóa kho hàng trên web, iOS và Android bằng cách sử dụng trình lắng nghe Firestore. Khi kho hàng thay đổi, tất cả các client tự động cập nhật. Tính năng bền vững ngoại tuyến đảm bảo nhân viên kho có thể quét các mặt hàng mà không cần kết nối, với tính năng đồng bộ hóa tự động khi được kết nối lại.
SaaS: Xác thực đa khách thuê
Một nền tảng B2B sử dụng Firebase Auth với các xác nhận tùy chỉnh cho quyền truy cập dựa trên vai trò. Người dùng quản trị nhận được các quyền nâng cao thông qua Cloud Functions xác thực dựa trên cấu hình khách thuê Firestore. Một codebase duy nhất phục vụ hơn 500 tổ chức với dữ liệu được cô lập.
Kết luận
Tích hợp API Firebase liên quan đến bốn dịch vụ cốt lõi:
- Xác thực: Đăng nhập bằng Email, Google, Apple với mã thông báo JWT
- Firestore: Cơ sở dữ liệu NoSQL với trình lắng nghe thời gian thực và quy tắc bảo mật
- Cloud Functions: Backend phi máy chủ được kích hoạt bởi các sự kiện hoặc HTTP
- Storage: Tải lên tệp với phân phối CDN
Bạn đã học về các luồng xác thực, các thao tác cơ sở dữ liệu, triển khai hàm và quản lý tệp. Bạn đã thấy các mẫu sản xuất: xử lý lỗi, nhóm, hỗ trợ ngoại tuyến và bảo mật.
nút
FAQ
Firebase có miễn phí để sử dụng không?
Có, Firebase có một gói miễn phí hào phóng (Spark Plan) bao gồm 5GB lưu trữ, 50K lượt đọc Firestore/ngày, 2M lượt gọi Cloud Function và 10K người dùng Auth/tháng. Các gói trả phí (Blaze) sử dụng giá trả theo mức sử dụng.
Tôi có thể sử dụng Firebase với các cơ sở dữ liệu hiện có không?
Có. Sử dụng Firebase Extensions để đồng bộ hóa với PostgreSQL, MySQL hoặc MongoDB. Hoặc gọi các API bên ngoài từ Cloud Functions để tích hợp với các hệ thống hiện có.
Làm cách nào để di chuyển từ Firebase sang nền tảng khác?
Xuất dữ liệu bằng các chức năng xuất của Firestore hoặc Firebase CLI. Đối với các tập dữ liệu lớn, hãy sử dụng quy trình xuất Dataflow. Độ phức tạp của việc di chuyển phụ thuộc vào cấu trúc dữ liệu của bạn.
Firebase có hỗ trợ GraphQL không?
Không natively. Sử dụng các giải pháp của bên thứ ba như firestore-graphql hoặc xây dựng một lớp GraphQL bằng Cloud Functions và Apollo Server.
Tôi có thể sử dụng Firebase tại chỗ không?
Không. Firebase chỉ dành cho Google Cloud. Đối với các lựa chọn thay thế tự lưu trữ, hãy cân nhắc Appwrite, Supabase hoặc Nhost.
Làm cách nào để xử lý các tệp tải lên lớn hơn 100MB?
Sử dụng tính năng tải lên có thể tiếp tục với chia nhỏ tệp (chunking). Firebase SDK tự động xử lý việc này. Đối với các tệp rất lớn, hãy sử dụng Google Cloud Storage trực tiếp với các URL được ký.
Điều gì sẽ xảy ra nếu tôi vượt quá giới hạn truy vấn Firestore?
Các truy vấn sẽ thất bại với lỗi FAILED_PRECONDITION. Thêm các chỉ mục cần thiết hoặc cấu trúc lại các truy vấn. Firestore cung cấp các liên kết trực tiếp để tạo các chỉ mục còn thiếu trong thông báo lỗi.
Firebase có tuân thủ GDPR không?
Có, Firebase cung cấp khả năng xử lý dữ liệu tuân thủ GDPR. Bật tính năng lưu trữ dữ liệu ở các khu vực cụ thể, triển khai xuất/xóa dữ liệu người dùng và ký Thỏa thuận Xử lý Dữ liệu của Google.
