🗄️
데이터베이스
Firebase
Google의 모바일/웹 앱 개발 플랫폼으로, 백엔드 인프라 없이도 인증, 데이터베이스, 스토리지, 호스팅, 서버리스 함수 등을 통합 제공하는 BaaS(Backend-as-a-Service)입니다.
Google의 모바일/웹 앱 개발 플랫폼으로, 백엔드 인프라 없이도 인증, 데이터베이스, 스토리지, 호스팅, 서버리스 함수 등을 통합 제공하는 BaaS(Backend-as-a-Service)입니다.
Firebase는 2011년 실시간 데이터베이스 서비스로 시작해 2014년 Google에 인수된 후 종합적인 앱 개발 플랫폼으로 발전했습니다. 스타트업과 MVP 개발에 특히 인기 있으며, 백엔드 서버 없이도 모바일/웹 앱의 전체 기능을 구현할 수 있습니다.
| 특성 | Firestore | Realtime Database |
|---|---|---|
| 데이터 모델 | Document-Collection | JSON Tree |
| 쿼리 | 복합 쿼리, 인덱싱 지원 | 제한적 (정렬 1개만) |
| 확장성 | 자동 수평 확장 | 수직 확장 (샤딩 수동) |
| 오프라인 | 다중 탭, 영구 캐시 | 단일 탭 |
| 과금 | 읽기/쓰기 횟수 | 대역폭/저장 용량 |
// Firebase SDK 초기화
import { initializeApp } from 'firebase/app';
import {
getFirestore, collection, doc,
addDoc, getDoc, getDocs, updateDoc, deleteDoc,
query, where, orderBy, limit, onSnapshot
} from 'firebase/firestore';
const firebaseConfig = {
apiKey: "your-api-key",
authDomain: "your-app.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-app.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123"
};
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
// CREATE - 문서 추가
async function addUser(userData) {
const docRef = await addDoc(collection(db, 'users'), {
name: userData.name,
email: userData.email,
createdAt: new Date(),
status: 'active'
});
console.log('Document ID:', docRef.id);
return docRef.id;
}
// READ - 단일 문서 조회
async function getUser(userId) {
const docSnap = await getDoc(doc(db, 'users', userId));
if (docSnap.exists()) {
return { id: docSnap.id, ...docSnap.data() };
}
return null;
}
// READ - 컬렉션 쿼리 (조건 검색)
async function getActiveUsers(minAge) {
const q = query(
collection(db, 'users'),
where('status', '==', 'active'),
where('age', '>=', minAge),
orderBy('age'),
limit(10)
);
const snapshot = await getDocs(q);
return snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
}
// UPDATE - 문서 수정
async function updateUser(userId, updates) {
await updateDoc(doc(db, 'users', userId), {
...updates,
updatedAt: new Date()
});
}
// DELETE - 문서 삭제
async function deleteUser(userId) {
await deleteDoc(doc(db, 'users', userId));
}// 실시간 채팅 리스너
function subscribeToChatRoom(roomId, onMessage) {
const messagesRef = collection(db, 'rooms', roomId, 'messages');
const q = query(messagesRef, orderBy('timestamp', 'desc'), limit(50));
// onSnapshot으로 실시간 변경 감지
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'added') {
onMessage({
type: 'new',
message: { id: change.doc.id, ...change.doc.data() }
});
}
if (change.type === 'modified') {
onMessage({
type: 'updated',
message: { id: change.doc.id, ...change.doc.data() }
});
}
if (change.type === 'removed') {
onMessage({
type: 'deleted',
messageId: change.doc.id
});
}
});
}, (error) => {
console.error('Realtime listener error:', error);
});
// 컴포넌트 언마운트 시 구독 해제
return unsubscribe;
}
// React 컴포넌트에서 사용
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const unsubscribe = subscribeToChatRoom(roomId, (event) => {
if (event.type === 'new') {
setMessages(prev => [event.message, ...prev]);
}
});
return () => unsubscribe();
}, [roomId]);
}import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
onAuthStateChanged,
signOut
} from 'firebase/auth';
const auth = getAuth(app);
const googleProvider = new GoogleAuthProvider();
// 이메일/비밀번호 회원가입
async function signUp(email, password) {
try {
const result = await createUserWithEmailAndPassword(auth, email, password);
// Firestore에 사용자 프로필 생성
await addDoc(collection(db, 'users'), {
uid: result.user.uid,
email: result.user.email,
createdAt: new Date()
});
return result.user;
} catch (error) {
if (error.code === 'auth/email-already-in-use') {
throw new Error('이미 사용 중인 이메일입니다.');
}
throw error;
}
}
// Google 소셜 로그인
async function signInWithGoogle() {
const result = await signInWithPopup(auth, googleProvider);
return result.user;
}
// 인증 상태 감지
onAuthStateChanged(auth, (user) => {
if (user) {
console.log('로그인됨:', user.email);
} else {
console.log('로그아웃 상태');
}
});const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
// HTTP 트리거 함수
exports.api = functions.https.onRequest(async (req, res) => {
if (req.method !== 'POST') {
return res.status(405).send('Method Not Allowed');
}
const { userId, action } = req.body;
// 비즈니스 로직 처리
res.json({ success: true });
});
// Firestore 트리거 - 문서 생성 시
exports.onUserCreated = functions.firestore
.document('users/{userId}')
.onCreate(async (snap, context) => {
const userData = snap.data();
// 환영 이메일 발송 (외부 서비스 호출)
await sendWelcomeEmail(userData.email);
// 통계 업데이트
await admin.firestore()
.collection('stats')
.doc('users')
.update({
totalCount: admin.firestore.FieldValue.increment(1)
});
});
// 스케줄 함수 - 매일 자정 실행
exports.dailyCleanup = functions.pubsub
.schedule('0 0 * * *')
.timeZone('Asia/Seoul')
.onRun(async (context) => {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
// 30일 이상 된 로그 삭제
const snapshot = await admin.firestore()
.collection('logs')
.where('createdAt', '<', thirtyDaysAgo)
.get();
const batch = admin.firestore().batch();
snapshot.docs.forEach(doc => batch.delete(doc.ref));
await batch.commit();
console.log(`Deleted ${snapshot.size} old logs`);
});rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// 사용자 프로필 - 본인만 읽기/쓰기
match /users/{userId} {
allow read, write: if request.auth != null
&& request.auth.uid == userId;
}
// 게시글 - 인증 사용자만 작성, 모두 읽기
match /posts/{postId} {
allow read: if true;
allow create: if request.auth != null
&& request.resource.data.authorId == request.auth.uid;
allow update, delete: if request.auth != null
&& resource.data.authorId == request.auth.uid;
}
// 채팅방 - 참가자만 접근
match /rooms/{roomId} {
allow read, write: if request.auth != null
&& request.auth.uid in resource.data.members;
match /messages/{messageId} {
allow read: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)).data.members;
allow create: if request.auth != null
&& request.auth.uid in get(/databases/$(database)/documents/rooms/$(roomId)).data.members;
}
}
}
}Firebase의 독자적 API와 데이터 모델에 종속되면 이전이 어렵습니다. 핵심 비즈니스 로직은 분리하고, 인터페이스 레이어를 두는 것이 좋습니다.
Firestore는 읽기/쓰기 횟수 기반 과금입니다. 복잡한 쿼리, 실시간 리스너, 대용량 데이터에서 예상치 못한 비용이 발생할 수 있습니다. Budget Alert를 반드시 설정하세요.
Security Rules는 클라이언트 요청을 필터링하지만, 복잡한 비즈니스 검증이나 트랜잭션 보장에는 한계가 있습니다. 중요한 로직은 Cloud Functions로 처리하세요.
Firestore는 복합 쿼리 시 인덱스가 필요하고, OR 조건 쿼리, 전문 검색(Full-text search) 등이 제한됩니다. 복잡한 검색에는 Algolia 등 외부 서비스 연동이 필요합니다.