Skip to main content
Back to Blog
26 July 202514 min read

Firebase: Building Real-Time Backend Services

FirebaseGCPServerlessReal-time

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 Testing

Firestore 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

  1. Security rules first: Design Firestore rules before writing client code

  2. Real-time by default: Leverage onSnapshot for live data updates

  3. Subcollections: Use subcollections for one-to-many relationships

  4. Batch operations: Use batched writes for atomic operations

  5. Cloud Functions: Keep functions focused and idempotent

  6. Authentication integration: Firebase Auth integrates seamlessly with other services

  7. Offline support: Enable offline persistence for better UX

  8. 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.

Share this article