Firebase - Google의 Backend-as-a-Service 플랫폼
Firebase는 Google의 포괄적인 Backend-as-a-Service (BaaS) 플랫폼으로, 클라우드 기반 도구와 서비스 제공을 통해 애플리케이션 개발을 가속화하도록 설계되었습니다. 원래 Firebase Inc.에서 개발되어 2014년 Google에 인수된 Firebase는 백엔드 인프라, 실시간 데이터 동기화, 인증, 호스팅, 분석 등을 처리하는 완전한 개발 플랫폼으로 발전했습니다. Firebase는 두 가지 주요 데이터베이스 솔루션을 제공합니다: 원본 Realtime Database와 더 고급화된 Cloud Firestore로, 둘 다 오프라인 기능과 여러 클라이언트 간 원활한 동기화를 지원하는 실시간 애플리케이션을 위해 설계되었습니다.
Firebase 시작하기
프로젝트 설정 및 구성
// Install Firebase CLI
npm install -g firebase-tools
// Login to Firebase
firebase login
// Initialize Firebase project
firebase init
// Install Firebase SDK for web
npm install firebase
// Initialize Firebase in your application
import \\\\{ initializeApp \\\\} from 'firebase/app';
import \\\\{ getFirestore \\\\} from 'firebase/firestore';
import \\\\{ getAuth \\\\} from 'firebase/auth';
import \\\\{ getStorage \\\\} from 'firebase/storage';
const firebaseConfig = \\\\{
apiKey: "your-api-key",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project-default-rtdb.firebaseio.com",
projectId: "your-project-id",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "your-app-id"
\\\\};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize services
const db = getFirestore(app);
const auth = getAuth(app);
const storage = getStorage(app);
export \\\\{ db, auth, storage \\\\};
Firebase 콘솔 및 프로젝트 관리
// Firebase project structure
// - Authentication: User management and sign-in
// - Firestore Database: NoSQL document database
// - Realtime Database: JSON tree database
// - Storage: File storage service
// - Hosting: Web hosting service
// - Functions: Serverless functions
// - Analytics: App usage analytics
// - Performance: App performance monitoring
// Environment configuration
// Development
const devConfig = \\\\{
apiKey: "dev-api-key",
authDomain: "dev-project.firebaseapp.com",
projectId: "dev-project-id"
\\\\};
// Production
const prodConfig = \\\\{
apiKey: "prod-api-key",
authDomain: "prod-project.firebaseapp.com",
projectId: "prod-project-id"
\\\\};
const config = process.env.NODE_ENV === 'production' ? prodConfig : devConfig;
const app = initializeApp(config);
Cloud Firestore (권장)
데이터베이스 구조 및 컬렉션
// Firestore data model: Collections -> Documents -> Fields
// Collections contain documents
// Documents contain fields and subcollections
// Documents are identified by unique IDs
import \\\\{
collection,
doc,
addDoc,
setDoc,
getDoc,
getDocs,
updateDoc,
deleteDoc,
query,
where,
orderBy,
limit,
onSnapshot
\\\\} from 'firebase/firestore';
// Reference to a collection
const usersRef = collection(db, 'users');
// Reference to a specific document
const userRef = doc(db, 'users', 'user123');
// Reference to a subcollection
const postsRef = collection(db, 'users', 'user123', 'posts');
// Create document with auto-generated ID
const createUser = async (userData) => \\\\{
try \\\\{
const docRef = await addDoc(collection(db, 'users'), \\\\{
name: userData.name,
email: userData.email,
createdAt: new Date(),
isActive: true,
profile: \\\\{
bio: userData.bio||'',
avatar: userData.avatar||'',
preferences: \\\\{
theme: 'light',
notifications: true
\\\\}
\\\\}
\\\\});
console.log('Document written with ID: ', docRef.id);
return docRef.id;
\\\\} catch (error) \\\\{
console.error('Error adding document: ', error);
throw error;
\\\\}
\\\\};
// Create document with custom ID
const createUserWithId = async (userId, userData) => \\\\{
try \\\\{
await setDoc(doc(db, 'users', userId), \\\\{
name: userData.name,
email: userData.email,
createdAt: new Date(),
isActive: true
\\\\});
console.log('Document created with custom ID: ', userId);
\\\\} catch (error) \\\\{
console.error('Error creating document: ', error);
throw error;
\\\\}
\\\\};
CRUD 작업
// CREATE - Add documents
const addPost = async (userId, postData) => \\\\{
try \\\\{
const postRef = await addDoc(collection(db, 'users', userId, 'posts'), \\\\{
title: postData.title,
content: postData.content,
tags: postData.tags||[],
publishedAt: new Date(),
isPublished: false,
likes: 0,
comments: []
\\\\});
return postRef.id;
\\\\} catch (error) \\\\{
console.error('Error adding post: ', error);
throw error;
\\\\}
\\\\};
// READ - Get single document
const getUser = async (userId) => \\\\{
try \\\\{
const docSnap = await getDoc(doc(db, 'users', userId));
if (docSnap.exists()) \\\\{
return \\\\{ id: docSnap.id, ...docSnap.data() \\\\};
\\\\} else \\\\{
console.log('No such document!');
return null;
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting document: ', error);
throw error;
\\\\}
\\\\};
// READ - Get multiple documents
const getAllUsers = async () => \\\\{
try \\\\{
const querySnapshot = await getDocs(collection(db, 'users'));
const users = [];
querySnapshot.forEach((doc) => \\\\{
users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\});
return users;
\\\\} catch (error) \\\\{
console.error('Error getting documents: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update document
const updateUser = async (userId, updates) => \\\\{
try \\\\{
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, \\\\{
...updates,
updatedAt: new Date()
\\\\});
console.log('Document updated successfully');
\\\\} catch (error) \\\\{
console.error('Error updating document: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update nested fields
const updateUserProfile = async (userId, profileUpdates) => \\\\{
try \\\\{
const userRef = doc(db, 'users', userId);
await updateDoc(userRef, \\\\{
'profile.bio': profileUpdates.bio,
'profile.avatar': profileUpdates.avatar,
'profile.preferences.theme': profileUpdates.theme,
updatedAt: new Date()
\\\\});
\\\\} catch (error) \\\\{
console.error('Error updating profile: ', error);
throw error;
\\\\}
\\\\};
// DELETE - Delete document
const deleteUser = async (userId) => \\\\{
try \\\\{
await deleteDoc(doc(db, 'users', userId));
console.log('Document deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting document: ', error);
throw error;
\\\\}
\\\\};
쿼리 및 필터링
import \\\\{
query,
where,
orderBy,
limit,
startAt,
endAt,
startAfter,
endBefore
\\\\} from 'firebase/firestore';
// Simple queries
const getActiveUsers = async () => \\\\{
const q = query(
collection(db, 'users'),
where('isActive', '==', true)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Compound queries
const getRecentActivePosts = async () => \\\\{
const q = query(
collection(db, 'posts'),
where('isPublished', '==', true),
where('publishedAt', '>', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)),
orderBy('publishedAt', 'desc'),
limit(10)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Array queries
const getPostsByTags = async (tags) => \\\\{
const q = query(
collection(db, 'posts'),
where('tags', 'array-contains-any', tags)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Range queries
const getUsersByAgeRange = async (minAge, maxAge) => \\\\{
const q = query(
collection(db, 'users'),
where('age', '>=', minAge),
where('age', '<=', maxAge),
orderBy('age')
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
// Pagination
const getPaginatedUsers = async (pageSize = 10, lastDoc = null) => \\\\{
let q = query(
collection(db, 'users'),
orderBy('createdAt', 'desc'),
limit(pageSize)
);
if (lastDoc) \\\\{
q = query(
collection(db, 'users'),
orderBy('createdAt', 'desc'),
startAfter(lastDoc),
limit(pageSize)
);
\\\\}
const querySnapshot = await getDocs(q);
const users = querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
return \\\\{ users, lastDoc: lastVisible, hasMore: users.length === pageSize \\\\};
\\\\};
// Text search (limited - consider Algolia for full-text search)
const searchUsersByName = async (searchTerm) => \\\\{
const q = query(
collection(db, 'users'),
where('name', '>=', searchTerm),
where('name', '<=', searchTerm + '\uf8ff'),
orderBy('name'),
limit(10)
);
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map(doc => (\\\\{ id: doc.id, ...doc.data() \\\\}));
\\\\};
실시간 리스너
// Real-time listener for single document
const subscribeToUser = (userId, callback) => \\\\{
const userRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(userRef, (doc) => \\\\{
if (doc.exists()) \\\\{
callback(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\} else \\\\{
callback(null);
\\\\}
\\\\}, (error) => \\\\{
console.error('Error listening to user: ', error);
\\\\});
return unsubscribe; // Call this function to stop listening
\\\\};
// Real-time listener for collection
const subscribeToActiveUsers = (callback) => \\\\{
const q = query(
collection(db, 'users'),
where('isActive', '==', true),
orderBy('createdAt', 'desc')
);
const unsubscribe = onSnapshot(q, (querySnapshot) => \\\\{
const users = [];
querySnapshot.forEach((doc) => \\\\{
users.push(\\\\{ id: doc.id, ...doc.data() \\\\});
\\\\});
callback(users);
\\\\}, (error) => \\\\{
console.error('Error listening to users: ', error);
\\\\});
return unsubscribe;
\\\\};
// Listen to document changes with metadata
const subscribeToUserWithMetadata = (userId, callback) => \\\\{
const userRef = doc(db, 'users', userId);
const unsubscribe = onSnapshot(userRef, \\\\{ includeMetadataChanges: true \\\\}, (doc) => \\\\{
const source = doc.metadata.hasPendingWrites ? 'Local' : 'Server';
const data = doc.exists() ? \\\\{ id: doc.id, ...doc.data() \\\\} : null;
callback(\\\\{ data, source, fromCache: doc.metadata.fromCache \\\\});
\\\\});
return unsubscribe;
\\\\};
// React hook example for real-time data
const useUser = (userId) => \\\\{
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => \\\\{
if (!userId) \\\\{
setUser(null);
setLoading(false);
return;
\\\\}
const unsubscribe = subscribeToUser(userId, (userData) => \\\\{
setUser(userData);
setLoading(false);
setError(null);
\\\\});
return () => unsubscribe();
\\\\}, [userId]);
return \\\\{ user, loading, error \\\\};
\\\\};
트랜잭션 및 배치 작업
import \\\\{
runTransaction,
writeBatch,
increment,
arrayUnion,
arrayRemove,
serverTimestamp
\\\\} from 'firebase/firestore';
// Transaction example - Transfer points between users
const transferPoints = async (fromUserId, toUserId, points) => \\\\{
try \\\\{
await runTransaction(db, async (transaction) => \\\\{
const fromUserRef = doc(db, 'users', fromUserId);
const toUserRef = doc(db, 'users', toUserId);
const fromUserDoc = await transaction.get(fromUserRef);
const toUserDoc = await transaction.get(toUserRef);
if (!fromUserDoc.exists()||!toUserDoc.exists()) \\\\{
throw new Error('One or both users do not exist');
\\\\}
const fromUserPoints = fromUserDoc.data().points||0;
if (fromUserPoints < points) \\\\{
throw new Error('Insufficient points');
\\\\}
transaction.update(fromUserRef, \\\\{
points: fromUserPoints - points,
updatedAt: serverTimestamp()
\\\\});
transaction.update(toUserRef, \\\\{
points: increment(points),
updatedAt: serverTimestamp()
\\\\});
\\\\});
console.log('Points transferred successfully');
\\\\} catch (error) \\\\{
console.error('Transaction failed: ', error);
throw error;
\\\\}
\\\\};
// Batch operations
const batchUpdateUsers = async (userUpdates) => \\\\{
const batch = writeBatch(db);
userUpdates.forEach((\\\\{ userId, updates \\\\}) => \\\\{
const userRef = doc(db, 'users', userId);
batch.update(userRef, \\\\{
...updates,
updatedAt: serverTimestamp()
\\\\});
\\\\});
try \\\\{
await batch.commit();
console.log('Batch update completed');
\\\\} catch (error) \\\\{
console.error('Batch update failed: ', error);
throw error;
\\\\}
\\\\};
// Array operations
const addTagToPost = async (postId, tag) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
tags: arrayUnion(tag),
updatedAt: serverTimestamp()
\\\\});
\\\\};
const removeTagFromPost = async (postId, tag) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
tags: arrayRemove(tag),
updatedAt: serverTimestamp()
\\\\});
\\\\};
// Increment/decrement operations
const likePost = async (postId) => \\\\{
const postRef = doc(db, 'posts', postId);
await updateDoc(postRef, \\\\{
likes: increment(1),
updatedAt: serverTimestamp()
\\\\});
\\\\};
Firebase Realtime Database
데이터베이스 구조 및 참조
import \\\\{
getDatabase,
ref,
set,
get,
push,
update,
remove,
on,
off,
child,
orderByChild,
orderByKey,
orderByValue,
limitToFirst,
limitToLast,
startAt,
endAt,
equalTo
\\\\} from 'firebase/database';
// Initialize Realtime Database
const rtdb = getDatabase();
// Database structure (JSON tree)
// \\\\{
// "users": \\\\{
// "user1": \\\\{
// "name": "John Doe",
// "email": "john@example.com",
// "posts": \\\\{
// "post1": true,
// "post2": true
// \\\\}
// \\\\}
// \\\\},
// "posts": \\\\{
// "post1": \\\\{
// "title": "First Post",
// "content": "Hello World",
// "author": "user1",
// "timestamp": 1642694400000
// \\\\}
// \\\\}
// \\\\}
// Create references
const usersRef = ref(rtdb, 'users');
const userRef = ref(rtdb, 'users/user1');
const postsRef = ref(rtdb, 'posts');
Realtime Database의 CRUD 작업
// CREATE/UPDATE - Set data
const createUser = async (userId, userData) => \\\\{
try \\\\{
await set(ref(rtdb, `users/$\\{userId\\}`), \\\\{
name: userData.name,
email: userData.email,
createdAt: Date.now(),
isActive: true
\\\\});
console.log('User created successfully');
\\\\} catch (error) \\\\{
console.error('Error creating user: ', error);
throw error;
\\\\}
\\\\};
// CREATE - Push data (auto-generated key)
const addPost = async (postData) => \\\\{
try \\\\{
const newPostRef = push(ref(rtdb, 'posts'));
await set(newPostRef, \\\\{
title: postData.title,
content: postData.content,
author: postData.author,
timestamp: Date.now(),
likes: 0
\\\\});
return newPostRef.key;
\\\\} catch (error) \\\\{
console.error('Error adding post: ', error);
throw error;
\\\\}
\\\\};
// READ - Get data once
const getUser = async (userId) => \\\\{
try \\\\{
const snapshot = await get(ref(rtdb, `users/$\\{userId\\}`));
if (snapshot.exists()) \\\\{
return \\\\{ id: userId, ...snapshot.val() \\\\};
\\\\} else \\\\{
console.log('No data available');
return null;
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting user: ', error);
throw error;
\\\\}
\\\\};
// READ - Get all users
const getAllUsers = async () => \\\\{
try \\\\{
const snapshot = await get(ref(rtdb, 'users'));
if (snapshot.exists()) \\\\{
const users = [];
snapshot.forEach((childSnapshot) => \\\\{
users.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return users;
\\\\} else \\\\{
return [];
\\\\}
\\\\} catch (error) \\\\{
console.error('Error getting users: ', error);
throw error;
\\\\}
\\\\};
// UPDATE - Update specific fields
const updateUser = async (userId, updates) => \\\\{
try \\\\{
await update(ref(rtdb, `users/$\\{userId\\}`), \\\\{
...updates,
updatedAt: Date.now()
\\\\});
console.log('User updated successfully');
\\\\} catch (error) \\\\{
console.error('Error updating user: ', error);
throw error;
\\\\}
\\\\};
// DELETE - Remove data
const deleteUser = async (userId) => \\\\{
try \\\\{
await remove(ref(rtdb, `users/$\\{userId\\}`));
console.log('User deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting user: ', error);
throw error;
\\\\}
\\\\};
실시간 리스너 및 쿼리
// Real-time listener
const subscribeToUser = (userId, callback) => \\\\{
const userRef = ref(rtdb, `users/$\\{userId\\}`);
const unsubscribe = on(userRef, 'value', (snapshot) => \\\\{
const data = snapshot.exists() ? \\\\{ id: userId, ...snapshot.val() \\\\} : null;
callback(data);
\\\\}, (error) => \\\\{
console.error('Error listening to user: ', error);
\\\\});
return () => off(userRef, 'value', unsubscribe);
\\\\};
// Listen to child events
const subscribeToUserPosts = (userId, callbacks) => \\\\{
const userPostsRef = ref(rtdb, `users/$\\{userId\\}/posts`);
const childAddedListener = on(userPostsRef, 'child_added', callbacks.onAdded);
const childChangedListener = on(userPostsRef, 'child_changed', callbacks.onChanged);
const childRemovedListener = on(userPostsRef, 'child_removed', callbacks.onRemoved);
return () => \\\\{
off(userPostsRef, 'child_added', childAddedListener);
off(userPostsRef, 'child_changed', childChangedListener);
off(userPostsRef, 'child_removed', childRemovedListener);
\\\\};
\\\\};
// Queries with ordering and filtering
const getTopPosts = async (limit = 10) => \\\\{
try \\\\{
const topPostsQuery = query(
ref(rtdb, 'posts'),
orderByChild('likes'),
limitToLast(limit)
);
const snapshot = await get(topPostsQuery);
const posts = [];
snapshot.forEach((childSnapshot) => \\\\{
posts.unshift(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return posts;
\\\\} catch (error) \\\\{
console.error('Error getting top posts: ', error);
throw error;
\\\\}
\\\\};
// Range queries
const getPostsByDateRange = async (startDate, endDate) => \\\\{
try \\\\{
const dateRangeQuery = query(
ref(rtdb, 'posts'),
orderByChild('timestamp'),
startAt(startDate),
endAt(endDate)
);
const snapshot = await get(dateRangeQuery);
const posts = [];
snapshot.forEach((childSnapshot) => \\\\{
posts.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return posts;
\\\\} catch (error) \\\\{
console.error('Error getting posts by date range: ', error);
throw error;
\\\\}
\\\\};
// Equal to query
const getUsersByStatus = async (status) => \\\\{
try \\\\{
const statusQuery = query(
ref(rtdb, 'users'),
orderByChild('status'),
equalTo(status)
);
const snapshot = await get(statusQuery);
const users = [];
snapshot.forEach((childSnapshot) => \\\\{
users.push(\\\\{
id: childSnapshot.key,
...childSnapshot.val()
\\\\});
\\\\});
return users;
\\\\} catch (error) \\\\{
console.error('Error getting users by status: ', error);
throw error;
\\\\}
\\\\};
Firebase 인증
인증 설정 및 방법
import \\\\{
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
FacebookAuthProvider,
TwitterAuthProvider,
signOut,
onAuthStateChanged,
updateProfile,
updatePassword,
sendPasswordResetEmail,
deleteUser
\\\\} from 'firebase/auth';
const auth = getAuth();
// Email/Password Authentication
const signUpWithEmail = async (email, password, displayName) => \\\\{
try \\\\{
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Update user profile
await updateProfile(user, \\\\{
displayName: displayName
\\\\});
// Create user document in Firestore
await setDoc(doc(db, 'users', user.uid), \\\\{
email: user.email,
displayName: displayName,
createdAt: new Date(),
isActive: true
\\\\});
return user;
\\\\} catch (error) \\\\{
console.error('Error signing up: ', error);
throw error;
\\\\}
\\\\};
const signInWithEmail = async (email, password) => \\\\{
try \\\\{
const userCredential = await signInWithEmailAndPassword(auth, email, password);
return userCredential.user;
\\\\} catch (error) \\\\{
console.error('Error signing in: ', error);
throw error;
\\\\}
\\\\};
// Social Authentication
const signInWithGoogle = async () => \\\\{
try \\\\{
const provider = new GoogleAuthProvider();
provider.addScope('profile');
provider.addScope('email');
const result = await signInWithPopup(auth, provider);
const user = result.user;
// Check if user document exists, create if not
const userDoc = await getDoc(doc(db, 'users', user.uid));
if (!userDoc.exists()) \\\\{
await setDoc(doc(db, 'users', user.uid), \\\\{
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
provider: 'google',
createdAt: new Date(),
isActive: true
\\\\});
\\\\}
return user;
\\\\} catch (error) \\\\{
console.error('Error signing in with Google: ', error);
throw error;
\\\\}
\\\\};
// Authentication state observer
const useAuthState = () => \\\\{
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => \\\\{
const unsubscribe = onAuthStateChanged(auth, (user) => \\\\{
setUser(user);
setLoading(false);
\\\\});
return () => unsubscribe();
\\\\}, []);
return \\\\{ user, loading \\\\};
\\\\};
// Sign out
const signOutUser = async () => \\\\{
try \\\\{
await signOut(auth);
console.log('User signed out successfully');
\\\\} catch (error) \\\\{
console.error('Error signing out: ', error);
throw error;
\\\\}
\\\\};
// Password reset
const resetPassword = async (email) => \\\\{
try \\\\{
await sendPasswordResetEmail(auth, email);
console.log('Password reset email sent');
\\\\} catch (error) \\\\{
console.error('Error sending password reset email: ', error);
throw error;
\\\\}
\\\\};
// Update user profile
const updateUserProfile = async (updates) => \\\\{
try \\\\{
const user = auth.currentUser;
if (user) \\\\{
await updateProfile(user, updates);
// Also update Firestore document
await updateDoc(doc(db, 'users', user.uid), \\\\{
...updates,
updatedAt: new Date()
\\\\});
\\\\}
\\\\} catch (error) \\\\{
console.error('Error updating profile: ', error);
throw error;
\\\\}
\\\\};
보안 규칙
// Firestore Security Rules
// rules_version = '2';
// service cloud.firestore \\\\{
// match /databases/\\\\{database\\\\}/documents \\\\{
// // Users can read and write their own data
// match /users/\\\\{userId\\\\} \\\\{
// allow read, write: if request.auth != null && request.auth.uid == userId;
// \\\\}
//
// // Posts are readable by all authenticated users
// // Only the author can write/update/delete
// match /posts/\\\\{postId\\\\} \\\\{
// allow read: if request.auth != null;
// allow create: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// allow update, delete: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// \\\\}
//
// // Comments can be created by authenticated users
// // Only the author can update/delete their comments
// match /posts/\\\\{postId\\\\}/comments/\\\\{commentId\\\\} \\\\{
// allow read: if request.auth != null;
// allow create: if request.auth != null;
// allow update, delete: if request.auth != null &&
// request.auth.uid == resource.data.authorId;
// \\\\}
// \\\\}
// \\\\}
// Realtime Database Security Rules
// \\\\{
// "rules": \\\\{
// "users": \\\\{
// "$uid": \\\\{
// ".read": "$uid === auth.uid",
// ".write": "$uid === auth.uid"
// \\\\}
// \\\\},
// "posts": \\\\{
// ".read": "auth != null",
// "$postId": \\\\{
// ".write": "auth != null && (
// !data.exists()||
// data.child('authorId').val() === auth.uid
// )"
// \\\\}
// \\\\}
// \\\\}
// \\\\}
// Custom claims for role-based access
const setUserRole = async (uid, role) => \\\\{
// This would be done in a Cloud Function with Admin SDK
// admin.auth().setCustomUserClaims(uid, \\\\{ role: role \\\\});
\\\\};
// Check user role in security rules
// allow write: if request.auth.token.role == 'admin';
Firebase 스토리지
파일 업로드 및 관리
import \\\\{
getStorage,
ref as storageRef,
uploadBytes,
uploadBytesResumable,
getDownloadURL,
deleteObject,
listAll,
getMetadata,
updateMetadata
\\\\} from 'firebase/storage';
const storage = getStorage();
// Upload file
const uploadFile = async (file, path) => \\\\{
try \\\\{
const fileRef = storageRef(storage, path);
const snapshot = await uploadBytes(fileRef, file);
const downloadURL = await getDownloadURL(snapshot.ref);
console.log('File uploaded successfully');
return \\\\{ downloadURL, fullPath: snapshot.ref.fullPath \\\\};
\\\\} catch (error) \\\\{
console.error('Error uploading file: ', error);
throw error;
\\\\}
\\\\};
// Upload with progress tracking
const uploadFileWithProgress = (file, path, onProgress) => \\\\{
return new Promise((resolve, reject) => \\\\{
const fileRef = storageRef(storage, path);
const uploadTask = uploadBytesResumable(fileRef, file);
uploadTask.on('state_changed',
(snapshot) => \\\\{
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
onProgress(progress);
\\\\},
(error) => \\\\{
console.error('Upload error: ', error);
reject(error);
\\\\},
async () => \\\\{
try \\\\{
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
resolve(\\\\{ downloadURL, fullPath: uploadTask.snapshot.ref.fullPath \\\\});
\\\\} catch (error) \\\\{
reject(error);
\\\\}
\\\\}
);
\\\\});
\\\\};
// Upload user avatar
const uploadUserAvatar = async (userId, file) => \\\\{
try \\\\{
const path = `avatars/$\\{userId\\}/$\\{file.name\\}`;
const result = await uploadFile(file, path);
// Update user profile with avatar URL
await updateDoc(doc(db, 'users', userId), \\\\{
photoURL: result.downloadURL,
updatedAt: new Date()
\\\\});
return result.downloadURL;
\\\\} catch (error) \\\\{
console.error('Error uploading avatar: ', error);
throw error;
\\\\}
\\\\};
// Delete file
const deleteFile = async (filePath) => \\\\{
try \\\\{
const fileRef = storageRef(storage, filePath);
await deleteObject(fileRef);
console.log('File deleted successfully');
\\\\} catch (error) \\\\{
console.error('Error deleting file: ', error);
throw error;
\\\\}
\\\\};
// List files in directory
const listFiles = async (path) => \\\\{
try \\\\{
const listRef = storageRef(storage, path);
const result = await listAll(listRef);
const files = await Promise.all(
result.items.map(async (itemRef) => \\\\{
const url = await getDownloadURL(itemRef);
const metadata = await getMetadata(itemRef);
return \\\\{
name: itemRef.name,
fullPath: itemRef.fullPath,
downloadURL: url,
size: metadata.size,
contentType: metadata.contentType,
timeCreated: metadata.timeCreated
\\\\};
\\\\})
);
return files;
\\\\} catch (error) \\\\{
console.error('Error listing files: ', error);
throw error;
\\\\}
\\\\};
Firebase Cloud Functions
함수 개발 및 배포
// Install Firebase Functions
// npm install -g firebase-tools
// firebase init functions
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// HTTP Cloud Function
exports.helloWorld = functions.https.onRequest((request, response) => \\\\{
response.send('Hello from Firebase!');
\\\\});
// Firestore Trigger
exports.onUserCreate = functions.firestore
.document('users/\\\\{userId\\\\}')
.onCreate(async (snap, context) => \\\\{
const userData = snap.data();
const userId = context.params.userId;
// Send welcome email
console.log(`New user created: $\\{userData.email\\}`);
// Create user stats document
await admin.firestore().collection('userStats').doc(userId).set(\\\\{
postsCount: 0,
likesReceived: 0,
joinedAt: admin.firestore.FieldValue.serverTimestamp()
\\\\});
\\\\});
// Scheduled Function
exports.dailyCleanup = functions.pubsub
.schedule('0 2 * * *') // Every day at 2 AM
.timeZone('America/New_York')
.onRun(async (context) => \\\\{
// Clean up old data
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30);
const oldPosts = await admin.firestore()
.collection('posts')
.where('createdAt', '<', cutoffDate)
.where('isDeleted', '==', true)
.get();
const batch = admin.firestore().batch();
oldPosts.docs.forEach(doc => \\\\{
batch.delete(doc.ref);
\\\\});
await batch.commit();
console.log(`Deleted $\\{oldPosts.size\\} old posts`);
\\\\});
// Callable Function
exports.addAdminRole = functions.https.onCall(async (data, context) => \\\\{
// Check if user is authenticated and is admin
if (!context.auth) \\\\{
throw new functions.https.HttpsError('unauthenticated', 'User must be authenticated');
\\\\}
if (!context.auth.token.admin) \\\\{
throw new functions.https.HttpsError('permission-denied', 'User must be admin');
\\\\}
try \\\\{
await admin.auth().setCustomUserClaims(data.uid, \\\\{ admin: true \\\\});
return \\\\{ message: 'Admin role added successfully' \\\\};
\\\\} catch (error) \\\\{
throw new functions.https.HttpsError('internal', 'Error adding admin role');
\\\\}
\\\\});
// Deploy functions
// firebase deploy --only functions
성능 최적화 및 모범 사례
데이터 모델링 모범 사례
// Firestore data modeling principles
// 1. Denormalization for read efficiency
const createPostWithAuthor = async (postData, authorData) => \\\\{
const postRef = await addDoc(collection(db, 'posts'), \\\\{
title: postData.title,
content: postData.content,
// Denormalize author data for efficient reads
author: \\\\{
id: authorData.id,
name: authorData.name,
avatar: authorData.avatar
\\\\},
createdAt: new Date(),
likes: 0,
commentsCount: 0
\\\\});
return postRef.id;
\\\\};
// 2. Use subcollections for one-to-many relationships
const addCommentToPost = async (postId, commentData) => \\\\{
const commentRef = await addDoc(
collection(db, 'posts', postId, 'comments'),
\\\\{
content: commentData.content,
author: commentData.author,
createdAt: new Date(),
likes: 0
\\\\}
);
// Update comment count in parent post
await updateDoc(doc(db, 'posts', postId), \\\\{
commentsCount: increment(1)
\\\\});
return commentRef.id;
\\\\};
// 3. Use arrays for small lists, subcollections for large lists
const addTagToPost = async (postId, tag) => \\\\{
await updateDoc(doc(db, 'posts', postId), \\\\{
tags: arrayUnion(tag)
\\\\});
\\\\};
// 4. Optimize for your queries
// Create composite indexes for compound queries
// Index: collection: posts, fields: isPublished ASC, createdAt DESC
const getPublishedPosts = async () => \\\\{
const q = query(
collection(db, 'posts'),
where('isPublished', '==', true),
orderBy('createdAt', 'desc'),
limit(20)
);
return await getDocs(q);
\\\\};
캐싱 및 오프라인 지원
import \\\\{ enableNetwork, disableNetwork \\\\} from 'firebase/firestore';
// Enable offline persistence
import \\\\{ initializeFirestore, persistentLocalCache \\\\} from 'firebase/firestore';
const db = initializeFirestore(app, \\\\{
localCache: persistentLocalCache()
\\\\});
// Handle online/offline state
const handleNetworkChange = async (isOnline) => \\\\{
try \\\\{
if (isOnline) \\\\{
await enableNetwork(db);
console.log('Network enabled');
\\\\} else \\\\{
await disableNetwork(db);
console.log('Network disabled');
\\\\}
\\\\} catch (error) \\\\{
console.error('Error changing network state: ', error);
\\\\}
\\\\};
// Listen for network changes
window.addEventListener('online', () => handleNetworkChange(true));
window.addEventListener('offline', () => handleNetworkChange(false));
// Cache management
const getCachedData = async (docRef) => \\\\{
try \\\\{
// Try to get from cache first
const cachedDoc = await getDoc(docRef, \\\\{ source: 'cache' \\\\});
if (cachedDoc.exists()) \\\\{
return \\\\{ data: cachedDoc.data(), fromCache: true \\\\};
\\\\}
\\\\} catch (error) \\\\{
console.log('No cached data available');
\\\\}
// Fallback to server
const serverDoc = await getDoc(docRef, \\\\{ source: 'server' \\\\});
return \\\\{ data: serverDoc.exists() ? serverDoc.data() : null, fromCache: false \\\\};
\\\\};
보안 및 성능 모니터링
// Performance monitoring
import \\\\{ getPerformance, trace \\\\} from 'firebase/performance';
const perf = getPerformance(app);
// Custom trace
const measureDatabaseOperation = async (operation) => \\\\{
const customTrace = trace(perf, 'database_operation');
customTrace.start();
try \\\\{
const result = await operation();
customTrace.putAttribute('success', 'true');
return result;
\\\\} catch (error) \\\\{
customTrace.putAttribute('success', 'false');
customTrace.putAttribute('error', error.message);
throw error;
\\\\} finally \\\\{
customTrace.stop();
\\\\}
\\\\};
// Usage
const getUserWithTrace = async (userId) => \\\\{
return await measureDatabaseOperation(async () => \\\\{
return await getDoc(doc(db, 'users', userId));
\\\\});
\\\\};
// Analytics
import \\\\{ getAnalytics, logEvent \\\\} from 'firebase/analytics';
const analytics = getAnalytics(app);
// Log custom events
const logUserAction = (action, parameters = \\\\{\\\\}) => \\\\{
logEvent(analytics, action, \\\\{
...parameters,
timestamp: Date.now()
\\\\});
\\\\};
// Usage
logUserAction('post_created', \\\\{
post_id: 'post123',
category: 'technology'
\\\\});
Firebase의 포괄적인 서비스 제품군은 개발자들에게 실시간 기능, 강력한 인증, 원활한 오프라인 지원을 갖춘 현대적이고 확장 가능한 애플리케이션을 구축할 수 있는 강력한 플랫폼을 제공합니다. Google Cloud Platform과의 통합 및 다양한 플랫폼에 걸친 광범위한 SDK 지원으로 간단한 프로토타입부터 수백만 사용자를 서비스하는 복잡한 프로덕션 준비 애플리케이션까지 신속한 애플리케이션 개발에 탁월한 선택입니다.