Firebase: Building Real-Time Backend Services
Implementing serverless backends with Firebase. Firestore, Authentication, Cloud Functions, and real-time data synchronisation for modern applications.
Firebase: Building Real-Time Backend Services
Firebase provides a comprehensive platform for building serverless applications with real-time capabilities. From authentication to databases to cloud functions, Firebase enables rapid development without managing infrastructure.
Firebase Services Overview
Core Services
Firebase Platform:
├── Build
│ ├── Authentication - User identity management
│ ├── Firestore - NoSQL document database
│ ├── Realtime Database - JSON tree database
│ ├── Cloud Storage - File storage
│ ├── Cloud Functions - Serverless compute
│ └── Hosting - Static web hosting
│
├── Release & Monitor
│ ├── Crashlytics - Crash reporting
│ ├── Performance Monitoring
│ ├── Test Lab - Device testing
│ └── App Distribution
│
└── Engage
├── Analytics - User analytics
├── Cloud Messaging - Push notifications
├── Remote Config - Feature flags
└── A/B TestingFirestore Database
Data Model Design
// firestore-models.ts
interface User {
id: string;
email: string;
displayName: string;
photoURL?: string;
createdAt: Timestamp;
updatedAt: Timestamp;
settings: UserSettings;
}
interface UserSettings {
notifications: boolean;
theme: 'light' | 'dark' | 'system';
language: string;
}
interface Organization {
id: string;
name: string;
ownerId: string;
members: OrganizationMember[];
createdAt: Timestamp;
}
interface OrganizationMember {
userId: string;
role: 'owner' | 'admin' | 'member';
joinedAt: Timestamp;
}
// Subcollection pattern for large datasets
// /organizations/{orgId}/projects/{projectId}
interface Project {
id: string;
name: string;
description: string;
status: 'active' | 'archived';
createdBy: string;
createdAt: Timestamp;
}Security Rules
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function isAuthenticated() {
return request.auth != null;
}
function isOwner(userId) {
return request.auth.uid == userId;
}
function hasRole(orgId, role) {
let org = get(/databases/$(database)/documents/organizations/$(orgId));
return org.data.members.hasAny([{
userId: request.auth.uid,
role: role
}]);
}
function isOrgMember(orgId) {
let org = get(/databases/$(database)/documents/organizations/$(orgId));
return org.data.members.hasAny([{userId: request.auth.uid}]);
}
// Users collection
match /users/{userId} {
allow read: if isAuthenticated() && isOwner(userId);
allow create: if isAuthenticated() && isOwner(userId);
allow update: if isAuthenticated() && isOwner(userId);
allow delete: if false; // Soft delete only
}
// Organizations collection
match /organizations/{orgId} {
allow read: if isAuthenticated() && isOrgMember(orgId);
allow create: if isAuthenticated();
allow update: if isAuthenticated() && hasRole(orgId, 'admin');
allow delete: if isAuthenticated() && hasRole(orgId, 'owner');
// Projects subcollection
match /projects/{projectId} {
allow read: if isAuthenticated() && isOrgMember(orgId);
allow create: if isAuthenticated() && hasRole(orgId, 'admin');
allow update: if isAuthenticated() && hasRole(orgId, 'admin');
allow delete: if isAuthenticated() && hasRole(orgId, 'owner');
}
}
// Public read, authenticated write
match /publicContent/{docId} {
allow read: if true;
allow write: if isAuthenticated();
}
}
}Real-Time Queries
// firestore-client.ts
import {
collection,
doc,
query,
where,
orderBy,
limit,
onSnapshot,
addDoc,
updateDoc,
deleteDoc,
serverTimestamp,
Timestamp
} from 'firebase/firestore';
import { db } from './firebase-config';
// Real-time listener for projects
export const subscribeToProjects = (
orgId: string,
callback: (projects: Project[]) => void
): (() => void) => {
const projectsRef = collection(db, 'organizations', orgId, 'projects');
const q = query(
projectsRef,
where('status', '==', 'active'),
orderBy('createdAt', 'desc'),
limit(50)
);
return onSnapshot(q, (snapshot) => {
const projects = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
} as Project));
callback(projects);
}, (error) => {
console.error('Error fetching projects:', error);
});
};
// Create project with transaction
export const createProject = async (
orgId: string,
data: Omit<Project, 'id' | 'createdAt'>
): Promise<string> => {
const projectsRef = collection(db, 'organizations', orgId, 'projects');
const docRef = await addDoc(projectsRef, {
...data,
createdAt: serverTimestamp()
});
return docRef.id;
};
// Batch writes
export const archiveMultipleProjects = async (
orgId: string,
projectIds: string[]
): Promise<void> => {
const batch = writeBatch(db);
for (const projectId of projectIds) {
const projectRef = doc(db, 'organizations', orgId, 'projects', projectId);
batch.update(projectRef, {
status: 'archived',
archivedAt: serverTimestamp()
});
}
await batch.commit();
};Cloud Functions
HTTP Functions
// functions/src/api.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import express from 'express';
import cors from 'cors';
admin.initializeApp();
const app = express();
app.use(cors({ origin: true }));
app.use(express.json());
// Authentication middleware
const authenticate = async (
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
const token = authHeader.split('Bearer ')[1];
const decodedToken = await admin.auth().verifyIdToken(token);
req.user = decodedToken;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
// Routes
app.get('/api/user/profile', authenticate, async (req, res) => {
try {
const userDoc = await admin.firestore()
.collection('users')
.doc(req.user!.uid)
.get();
if (!userDoc.exists) {
return res.status(404).json({ error: 'User not found' });
}
res.json(userDoc.data());
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/user/profile', authenticate, async (req, res) => {
try {
const { displayName, settings } = req.body;
await admin.firestore()
.collection('users')
.doc(req.user!.uid)
.update({
displayName,
settings,
updatedAt: admin.firestore.FieldValue.serverTimestamp()
});
res.json({ message: 'Profile updated' });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
export const api = functions
.region('europe-west1')
.runWith({
memory: '256MB',
timeoutSeconds: 60
})
.https.onRequest(app);Firestore Triggers
// functions/src/triggers.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
const db = admin.firestore();
// On user creation
export const onUserCreated = functions
.region('europe-west1')
.auth.user().onCreate(async (user) => {
// Create user document
await db.collection('users').doc(user.uid).set({
email: user.email,
displayName: user.displayName || '',
photoURL: user.photoURL || '',
createdAt: admin.firestore.FieldValue.serverTimestamp(),
settings: {
notifications: true,
theme: 'system',
language: 'en'
}
});
// Send welcome email
await db.collection('mail').add({
to: user.email,
template: {
name: 'welcome',
data: {
displayName: user.displayName || 'there'
}
}
});
});
// On document write
export const onProjectCreated = functions
.region('europe-west1')
.firestore
.document('organizations/{orgId}/projects/{projectId}')
.onCreate(async (snapshot, context) => {
const { orgId, projectId } = context.params;
const project = snapshot.data();
// Update organization project count
const orgRef = db.collection('organizations').doc(orgId);
await orgRef.update({
projectCount: admin.firestore.FieldValue.increment(1)
});
// Notify organization members
const orgDoc = await orgRef.get();
const members = orgDoc.data()?.members || [];
const notifications = members.map((member: any) =>
db.collection('notifications').add({
userId: member.userId,
type: 'project_created',
data: {
projectId,
projectName: project.name,
createdBy: project.createdBy
},
read: false,
createdAt: admin.firestore.FieldValue.serverTimestamp()
})
);
await Promise.all(notifications);
});
// Scheduled function
export const cleanupOldNotifications = functions
.region('europe-west1')
.pubsub.schedule('0 0 * * *') // Daily at midnight
.timeZone('Europe/Amsterdam')
.onRun(async () => {
const thirtyDaysAgo = admin.firestore.Timestamp.fromDate(
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
);
const oldNotifications = await db.collection('notifications')
.where('createdAt', '<', thirtyDaysAgo)
.where('read', '==', true)
.limit(500)
.get();
const batch = db.batch();
oldNotifications.docs.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();
console.log(`Deleted ${oldNotifications.size} old notifications`);
});Firebase Authentication
Auth Implementation
// auth-service.ts
import {
getAuth,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithPopup,
GoogleAuthProvider,
signOut,
sendPasswordResetEmail,
onAuthStateChanged,
User
} from 'firebase/auth';
import { app } from './firebase-config';
const auth = getAuth(app);
const googleProvider = new GoogleAuthProvider();
export const authService = {
// Email/password sign in
async signIn(email: string, password: string): Promise<User> {
const result = await signInWithEmailAndPassword(auth, email, password);
return result.user;
},
// Email/password sign up
async signUp(email: string, password: string): Promise<User> {
const result = await createUserWithEmailAndPassword(auth, email, password);
return result.user;
},
// Google sign in
async signInWithGoogle(): Promise<User> {
const result = await signInWithPopup(auth, googleProvider);
return result.user;
},
// Sign out
async signOut(): Promise<void> {
await signOut(auth);
},
// Password reset
async resetPassword(email: string): Promise<void> {
await sendPasswordResetEmail(auth, email);
},
// Auth state listener
onAuthStateChanged(callback: (user: User | null) => void): () => void {
return onAuthStateChanged(auth, callback);
},
// Get current user
getCurrentUser(): User | null {
return auth.currentUser;
},
// Get ID token
async getIdToken(): Promise<string | null> {
const user = auth.currentUser;
if (!user) return null;
return user.getIdToken();
}
};React Auth Hook
// useAuth.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { User } from 'firebase/auth';
import { authService } from './auth-service';
interface AuthContextType {
user: User | null;
loading: boolean;
signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string) => Promise<void>;
signInWithGoogle: () => Promise<void>;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | null>(null);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = authService.onAuthStateChanged((user) => {
setUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value: AuthContextType = {
user,
loading,
signIn: async (email, password) => {
await authService.signIn(email, password);
},
signUp: async (email, password) => {
await authService.signUp(email, password);
},
signInWithGoogle: async () => {
await authService.signInWithGoogle();
},
signOut: async () => {
await authService.signOut();
}
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};Key Takeaways
-
Security rules first: Design Firestore rules before writing client code
-
Real-time by default: Leverage onSnapshot for live data updates
-
Subcollections: Use subcollections for one-to-many relationships
-
Batch operations: Use batched writes for atomic operations
-
Cloud Functions: Keep functions focused and idempotent
-
Authentication integration: Firebase Auth integrates seamlessly with other services
-
Offline support: Enable offline persistence for better UX
-
Cost awareness: Understand pricing model and optimise reads/writes
Firebase enables rapid development of real-time applications. Proper security rules and data modeling are essential for production deployments.