Cloud Backend Engineering · Serverless · Event-driven

Cloud Architecture GCP · Firebase · Cloud Functions · Firestore · Vertex AI

Serverless by default. Event-driven by design. A complete field guide to building scalable cloud backends with the GCP + Firebase ecosystem — zero servers to manage, infinite scale to reach.

Cloud Functions Cloud Run Firestore Cloud Pub/Sub Firebase Auth Firebase Security Rules Cloud Storage Vertex AI Secret Manager Cloud Tasks Firebase Realtime DB BigQuery
🔥
Firebase Auth
Identity platform
Cloud Functions
Serverless compute
🏃
Cloud Run
Containerized workloads
🧠
Vertex AI
ML / Gemini API
🗄️
Firestore
NoSQL realtime DB
📨
Pub/Sub
Async messaging
🗂️
Cloud Storage
Object storage + CDN
🔐
Secret Manager
Secrets at runtime

01 — The Ecosystem

Service Selection Guide

GCP + Firebase offers dozens of services. These are the ones that matter for production backend architecture — and exactly when to use each one.

The serverless shift: There is no "server" to manage. Cloud Functions replace controllers. Firestore replaces SQL with a database. Pub/Sub replaces Celery. Firebase Auth replaces your JWT service. Cloud Run handles anything that needs a persistent process or WebSocket. You pay for exactly what you use, and the platform handles scaling to zero and to millions.
Cloud Functions (2nd gen)
GCP · Serverless compute
HTTP-triggered and event-triggered TypeScript/Python functions. 2nd gen uses Cloud Run under the hood — supports concurrency up to 1000 req/instance. Cold starts ~100ms with min-instances=1.
onRequest()onDocumentCreated()onMessagePublished()
🏃
Cloud Run
GCP · Containerized serverless
Stateful workloads, WebSocket servers, long-running jobs, heavy ML inference. Containerized — any language, any framework. Scales to zero. Use when Cloud Functions hit the 60-min timeout or need WebSocket.
WebSocket serverHeavy workloadsLong tasks
🔥
Firestore
Firebase · NoSQL realtime
Document-oriented NoSQL with real-time listeners. Collections → Documents → Subcollections. Offline-first SDKs. Security Rules enforce access control at the DB level. Automatic sharding and global replication.
onSnapshot()TransactionsSecurity Rules
🔑
Firebase Auth
Firebase · Identity
Managed authentication — email/password, Google Sign-In, Apple, phone OTP, SAML/OIDC for enterprise. Issues JWTs automatically. Admin SDK verifies tokens server-side. Custom claims for RBAC.
verifyIdToken()setCustomUserClaims()
📨
Cloud Pub/Sub
GCP · Async messaging
At-least-once async message delivery. Replaces Celery/RabbitMQ. Publishers push events, subscribers process them in Cloud Functions or Cloud Run. Dead-letter topics for failed messages. Ordered delivery option.
topic.publish()onMessagePublished()
📋
Cloud Tasks
GCP · Task queue
Exactly-once HTTP task queue with scheduling and deduplication. Better than Pub/Sub when you need: scheduled delay, deduplication, rate limiting, or guaranteed delivery to an HTTP endpoint.
createTask()scheduleTime
🔐
Secret Manager
GCP · Secrets
Versioned, encrypted secret storage. Cloud Functions access secrets at runtime via environment variables OR SDK calls. Never put secrets in code or environment config files. IAM controls access per function.
accessSecretVersion()runWith({secrets})
🧠
Vertex AI / Gemini
GCP · AI platform
Gemini API for generative AI, Vertex AI for custom model training and serving. Call from Cloud Functions or Cloud Run. Built-in streaming support. IAM-authenticated — no API keys needed inside GCP.
generateContent()streamGenerateContent()
🗂️
Cloud Storage
GCP · Object storage
S3-equivalent. Firebase Storage SDK wraps it with auth-gated access rules. Signed URLs for secure client-direct upload/download. Triggers Cloud Functions on object finalize/delete. Global CDN via Cloud CDN.
getSignedUrl()onObjectFinalized()
📊
BigQuery
GCP · Analytics warehouse
Petabyte-scale analytics. Stream events from Pub/Sub or export Firestore via BigQuery connector. Run complex aggregations that Firestore can't handle. Export reports to Cloud Storage. Separate from operational DB.
insertRows()createQueryJob()
Firebase Realtime DB
Firebase · Low-latency sync
JSON tree database with sub-10ms latency. Use for presence, typing indicators, live cursors — things that need real-time at scale. Not a general-purpose DB — use Firestore for structured data.
onValue()onDisconnect()
🌐
Cloud Armor + Load Balancer
GCP · Edge security
DDoS protection, WAF rules, IP allowlist/denylist, geographic restrictions. Sits in front of Cloud Run or Cloud Functions via HTTPS Load Balancer. Rate limiting at the edge — before traffic hits your functions.
WAF rulesRate limiting

02 — Project Structure

Function-first Layout

Organised by domain feature. Each feature exports HTTP functions, Firestore triggers, Pub/Sub subscribers, and scheduled functions. Shared infrastructure in common layers.

functions / src /
features/— one dir per domain
auth/
auth.functions.tsHTTP fn— onRequest login/register endpoints
auth.triggers.tsTrigger— beforeUserCreated, afterUserCreated
auth.service.ts— custom claims, user profile creation
products/
products.functions.tsHTTP fn
products.triggers.tsFirestore— onDocumentCreated/Updated/Deleted
products.service.ts
products.repository.ts— Firestore reads/writes
notifications/
notifications.subscribers.tsPub/Sub— onMessagePublished handlers
notifications.service.ts— FCM, email dispatch
ai/
ai.functions.tsHTTP fn
ai.service.tsVertex AI— Gemini API calls
scheduled/
cleanup.functions.tsScheduled— onSchedule cron jobs
shared/
firebase-admin.ts— singleton Admin SDK init
auth.middleware.tsAuth— verifyIdToken guard
errors.ts— typed HttpsError hierarchy
pubsub.ts— typed Pub/Sub publisher helper
secrets.ts— Secret Manager accessor
logger.ts— structured Google Cloud Logging
validate.ts— zod schema validation helper
firestore/— security rules + indexes
firestore.rulesSecurity
firestore.indexes.json
firebase.json— functions, hosting, rules deploy config
.firebaserc— project aliases (dev/staging/prod)
package.json

03 — Code Patterns

TypeScript Blueprints

Production-ready TypeScript. Every pattern uses the Firebase Admin SDK v12+ and Cloud Functions v2 APIs.

products.functions.ts — HTTP v2Cloud Functions v2
import { onRequest } from 'firebase-functions/v2/https'
import { requireAuth, requireRole } from '../shared/auth.middleware'
import { validate } from '../shared/validate'
import { ProductService } from './products.service'
import { z } from 'zod'

const CreateProductSchema = z.object({
  name:  z.string().min(1).max(200),
  price: z.number().positive(),
  sku:   z.string().min(1),
})

// Cloud Functions v2 — supports concurrency, longer timeouts
export const getProducts = onRequest(
  { region: 'us-central1', memory: '256MiB', minInstances: 1 },
  async (req, res) => {
    const user = await requireAuth(req, res)
    if (!user) return

    const products = await ProductService.list({
      userId: user.uid,
      page:   Number(req.query.page) || 1,
    })
    res.json({ success: true, data: products })
  }
)

export const createProduct = onRequest(
  { region: 'us-central1', memory: '256MiB' },
  async (req, res) => {
    const user = await requireAuth(req, res)
    if (!user) return
    if (req.method !== 'POST') {
      res.status(405).json({ error: 'Method not allowed' }); return
    }

    const body = validate(CreateProductSchema, req.body, res)
    if (!body) return

    const product = await ProductService.create(body, user.uid)
    res.status(201).json({ success: true, data: product })
  }
)
shared/auth.middleware.ts — guardshared
import { Request, Response } from 'firebase-functions/v2/https'
import { getAuth } from 'firebase-admin/auth'
import { HttpsError } from 'firebase-functions/v2/https'
import type { DecodedIdToken } from 'firebase-admin/auth'

export async function requireAuth(
  req: Request,
  res: Response,
): Promise<DecodedIdToken | null> {
  const token = req.headers.authorization?.split('Bearer ')[1]

  if (!token) {
    res.status(401).json({ error: 'Missing auth token' })
    return null
  }

  try {
    // Verifies signature, expiry, revocation
    const decoded = await getAuth().verifyIdToken(token, true)
    return decoded
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' })
    return null
  }
}

// RBAC — checks custom claims set via setCustomUserClaims
export function requireRole(
  user: DecodedIdToken,
  res: Response,
  ...roles: string[]
): boolean {
  if (!roles.includes(user.role as string)) {
    res.status(403).json({ error: 'Insufficient permissions' })
    return false
  }
  return true
}
Callable Functions — typed SDK callsalternative to HTTP
import { onCall, HttpsError } from 'firebase-functions/v2/https'
import { z } from 'zod'

const PurchaseSchema = z.object({
  productId: z.string(),
  quantity:  z.number().int().positive(),
})

// Callable — client SDK handles auth + serialization automatically
// No headers, no HTTP — call like a typed function from the client
export const purchaseProduct = onCall<z.infer<typeof PurchaseSchema>>(
  { region: 'us-central1', memory: '512MiB' },
  async (request) => {
    // auth is verified automatically by the SDK
    if (!request.auth) {
      throw new HttpsError('unauthenticated', 'Login required')
    }

    const input = PurchaseSchema.safeParse(request.data)
    if (!input.success) {
      throw new HttpsError('invalid-argument', input.error.message)
    }

    return OrderService.purchase(
      input.data, request.auth.uid
    )
  }
)

// Client (Flutter, React, etc.):
// const fn = httpsCallable(functions, 'purchaseProduct')
// const result = await fn({ productId, quantity })
products.triggers.ts — Firestoreevent-driven
import {
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
} from 'firebase-functions/v2/firestore'
import { publishEvent } from '../shared/pubsub'
import { logger } from '../shared/logger'

// Fires when a new product document is created
export const onProductCreated = onDocumentCreated(
  'products/{productId}',
  async (event) => {
    const data = event.data?.data()
    if (!data) return

    logger.info('Product created', { id: event.params.productId })

    // Publish to Pub/Sub — notif service handles it
    await publishEvent('product-events', {
      type:      'product.created',
      productId: event.params.productId,
      sellerId:  data.sellerId,
    })
  }
)

// Fires when any field in the order document changes
export const onOrderStatusChanged = onDocumentUpdated(
  'orders/{orderId}',
  async (event) => {
    const before = event.data?.before.data()
    const after  = event.data?.after.data()
    if (!before || !after) return

    // Only react to status field changes
    if (before.status === after.status) return

    await publishEvent('order-events', {
      type:     'order.status_changed',
      orderId:  event.params.orderId,
      from:     before.status,
      to:       after.status,
      userId:   after.userId,
    })
  }
)
auth.triggers.ts — Firebase Auth hooksauth trigger
import {
  beforeUserCreated,
  beforeUserSignedIn,
} from 'firebase-functions/v2/identity'
import { getAuth }       from 'firebase-admin/auth'
import { getFirestore } from 'firebase-admin/firestore'
import { HttpsError }   from 'firebase-functions/v2/https'

// Block registration from banned domains
export const beforeCreate = beforeUserCreated(async (event) => {
  const email = event.data.email ?? ''
  const bannedDomains = ['mailinator.com', 'tempmail.com']

  if (bannedDomains.some(d => email.endsWith(`@${d}`))) {
    throw new HttpsError('permission-denied', 'Email domain not allowed')
  }

  // Create user profile in Firestore automatically
  await getFirestore().collection('users').doc(event.data.uid).set({
    email,
    name:      event.data.displayName ?? '',
    role:      'user',
    createdAt: FieldValue.serverTimestamp(),
  })

  // Return custom claims — attached to JWT immediately
  return { customClaims: { role: 'user' } }
})

// Set RBAC custom claims on sign-in (refresh if role changed)
export const beforeSignIn = beforeUserSignedIn(async (event) => {
  const doc = await getFirestore()
    .collection('users').doc(event.data.uid).get()
  const role = doc.data()?.role ?? 'user'
  return { customClaims: { role } }
})
scheduled/cleanup.functions.tscron
import { onSchedule } from 'firebase-functions/v2/scheduler'
import { getFirestore, FieldValue, Timestamp }
  from 'firebase-admin/firestore'
import { logger } from '../shared/logger'

// Cloud Scheduler — cron syntax, runs in your region
export const cleanupExpiredSessions = onSchedule(
  { schedule: 'every 6 hours', region: 'us-central1' },
  async () => {
    const db  = getFirestore()
    const now = Timestamp.now()

    // Firestore bulk delete with batched writes (max 500/batch)
    const expired = await db.collection('sessions')
      .where('expiresAt', '<', now)
      .limit(500)
      .get()

    if (expired.empty) return

    const batch = db.batch()
    expired.docs.forEach(doc => batch.delete(doc.ref))
    await batch.commit()

    logger.info(`Cleaned ${expired.size} expired sessions`)
  }
)
auth.service.ts — custom claims RBACauth · rbac
import { getAuth } from 'firebase-admin/auth'
import { logger } from '../shared/logger'

export type UserRole = 'admin' | 'moderator' | 'user'

export class AuthService {

  static async setRole(uid: string, role: UserRole): Promise<void> {
    // Custom claims are embedded in the Firebase JWT
    // User must refresh their token to see new claims
    await getAuth().setCustomUserClaims(uid, { role })
    logger.info('Role updated', { uid, role })
  }

  static async revokeTokens(uid: string): Promise<void> {
    await getAuth().revokeRefreshTokens(uid)
    logger.info('Tokens revoked', { uid })
  }

  static async disableUser(uid: string): Promise<void> {
    await getAuth().updateUser(uid, { disabled: true })
    await AuthService.revokeTokens(uid)
  }

  static async verifyAndDecode(
    idToken: string,
    checkRevoked = true,
  ) {
    // checkRevoked=true validates against revocation time
    return getAuth().verifyIdToken(idToken, checkRevoked)
  }
}
shared/secrets.ts — Secret Managerinfra · secrets
import { SecretManagerServiceClient }
  from '@google-cloud/secret-manager'

const client = new SecretManagerServiceClient()
const cache = new Map<string, string>()

export async function getSecret(
  name: string,
  version = 'latest',
): Promise<string> {
  // Cache in memory for function lifetime (warm instance)
  if (cache.has(name)) return cache.get(name)!

  const projectId = process.env.GCLOUD_PROJECT
  const [secret]  = await client.accessSecretVersion({
    name: `projects/${projectId}/secrets/${name}/versions/${version}`,
  })

  const value = secret.payload?.data?.toString() ?? ''
  cache.set(name, value)
  return value
}

// Alternative — bind secrets as env vars in function config:
// export const myFn = onRequest(
//   { secrets: ['STRIPE_KEY', 'SENDGRID_KEY'] },
//   async (req, res) => {
//     const key = process.env.STRIPE_KEY  // auto-injected
//   }
// )
shared/pubsub.ts — typed publisherPub/Sub
import { PubSub } from '@google-cloud/pubsub'

const pubsub = new PubSub()

// Type-safe event publishing
export async function publishEvent<T extends object>(
  topicName: string,
  event: T,
  attributes: Record<string, string> = {},
): Promise<string> {
  const topic   = pubsub.topic(topicName)
  const payload = Buffer.from(JSON.stringify(event))
  return topic.publish(payload, {
    ...attributes,
    publishedAt: new Date().toISOString(),
  })
}

// Subscriber in notifications/subscribers.ts:
import { onMessagePublished } from 'firebase-functions/v2/pubsub'

export const onProductEvent = onMessagePublished(
  { topic: 'product-events', region: 'us-central1' },
  async (event) => {
    const data = event.data.message.json as {
      type: string; productId: string; sellerId: string
    }
    if (data.type === 'product.created') {
      await NotificationService.notifySeller(data.sellerId)
    }
  }
)
Cloud Tasks — scheduled HTTP tasksCloud Tasks
import { CloudTasksClient } from '@google-cloud/tasks'

const tasksClient = new CloudTasksClient()

export async function scheduleTask<T>(opts: {
  queue:       string
  handlerUrl:  string
  payload:     T
  delaySeconds?: number
  dedupId?:    string
}) {
  const project  = process.env.GCLOUD_PROJECT!
  const location = 'us-central1'
  const parent   = tasksClient.queuePath(project, location, opts.queue)

  const task: any = {
    httpRequest: {
      httpMethod: 'POST',
      url: opts.handlerUrl,
      headers: { 'Content-Type': 'application/json' },
      body: Buffer.from(JSON.stringify(opts.payload)).toString('base64'),
      oidcToken: { serviceAccountEmail: process.env.SERVICE_ACCOUNT! },
    },
  }

  if (opts.delaySeconds) {
    task.scheduleTime = {
      seconds: Date.now() / 1000 + opts.delaySeconds
    }
  }
  if (opts.dedupId) task.name = `${parent}/tasks/${opts.dedupId}`

  return tasksClient.createTask({ parent, task })
}
products.repository.ts — Firestoredata layer
import {
  getFirestore, FieldValue, Timestamp,
  type DocumentData, type QuerySnapshot,
} from 'firebase-admin/firestore'
import type { Product, CreateProductInput } from './products.types'

const db = () => getFirestore()

export class ProductRepository {

  static col() { return db().collection('products') }

  static async findById(id: string): Promise<Product | null> {
    const doc = await ProductRepository.col().doc(id).get()
    return doc.exists ? ProductRepository.fromDoc(doc) : null
  }

  static async create(
    data: CreateProductInput, uid: string
  ): Promise<Product> {
    const ref = ProductRepository.col().doc()
    const doc = {
      ...data,
      id:        ref.id,
      sellerId:  uid,
      createdAt: FieldValue.serverTimestamp(),
      updatedAt: FieldValue.serverTimestamp(),
    }
    await ref.set(doc)
    return { ...doc, id: ref.id } as Product
  }

  static async listBySeller(
    sellerId: string,
    opts: { limit?: number; startAfter?: Timestamp } = {},
  ): Promise<Product[]> {
    let q = ProductRepository.col()
      .where('sellerId', '==', sellerId)
      .orderBy('createdAt', 'desc')
      .limit(opts.limit ?? 20)
    if (opts.startAfter) q = q.startAfter(opts.startAfter)
    const snap = await q.get()
    return snap.docs.map(ProductRepository.fromDoc)
  }

  static fromDoc = (doc: DocumentData): Product =>
    ({ id: doc.id, ...doc.data() } as Product)
}
Firestore transactions + batchesatomic writes
import { getFirestore, FieldValue } from 'firebase-admin/firestore'

export class OrderService {

  // Atomic: decrement stock + create order in one transaction
  static async purchase(
    productId: string,
    quantity:  number,
    buyerId:   string,
  ) {
    const db = getFirestore()

    return db.runTransaction(async (txn) => {
      const productRef = db.collection('products').doc(productId)
      const product    = await txn.get(productRef)

      if (!product.exists) throw new Error('Product not found')

      const stock = product.data()!.stock
      if (stock < quantity) throw new Error('Insufficient stock')

      // Decrement stock atomically
      txn.update(productRef, {
        stock: FieldValue.increment(-quantity),
      })

      // Create order document
      const orderRef = db.collection('orders').doc()
      txn.set(orderRef, {
        productId, quantity, buyerId,
        status:    'pending',
        createdAt: FieldValue.serverTimestamp(),
      })

      return { orderId: orderRef.id }
    })
  }
}
firestore.rules — Security Rulescritical
// Firestore Security Rules — enforce at the database level
// These run on Google's servers — cannot be bypassed by clients
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Helper functions
    function isAuthenticated() {
      return request.auth != null;
    }
    function isOwner(userId) {
      return isAuthenticated() && request.auth.uid == userId;
    }
    function hasRole(role) {
      return isAuthenticated()
        && request.auth.token.role == role;
    }
    function validProduct() {
      return request.resource.data.keys().hasAll(['name', 'price'])
        && request.resource.data.name is string
        && request.resource.data.price is number
        && request.resource.data.price > 0;
    }

    // Users collection
    match /users/{userId} {
      allow read:   if isOwner(userId) || hasRole('admin');
      allow create: if isOwner(userId);
      allow update: if isOwner(userId)
        && !request.resource.data.diff(
             resource.data).affectedKeys()
           .hasAny(['role', 'uid']); // can't self-promote
      allow delete: if hasRole('admin');
    }

    // Products collection
    match /products/{productId} {
      allow read: if isAuthenticated();
      allow create: if isAuthenticated() && validProduct()
        && request.resource.data.sellerId == request.auth.uid;
      allow update: if isOwner(resource.data.sellerId)
        || hasRole('admin');
      allow delete: if isOwner(resource.data.sellerId)
        || hasRole('admin');
    }

    // Orders — users see own orders, admins see all
    match /orders/{orderId} {
      allow read: if isOwner(resource.data.buyerId)
        || isOwner(resource.data.sellerId)
        || hasRole('admin');
      allow create: if isAuthenticated()
        && request.resource.data.buyerId == request.auth.uid;
      allow update: if hasRole('admin'); // only admins update
    }
  }
}
Firebase Storage Rulesstorage
// storage.rules — enforce file access at the CDN level
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {

    // User profile images — owner read/write, public read
    match /users/{userId}/avatar/{filename} {
      allow read:  if true;   // public CDN
      allow write: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024  // 5MB
        && request.resource.contentType.matches('image/.*');
    }

    // Product images
    match /products/{productId}/{filename} {
      allow read: if true;
      allow write: if request.auth != null
        && request.resource.size < 10 * 1024 * 1024
        && request.resource.contentType.matches('image/.*');
    }

    // Private documents — only owner
    match /private/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null
        && request.auth.uid == userId;
    }
  }
}
ai.service.ts — Gemini APIVertex AI
import { VertexAI, HarmCategory, HarmBlockThreshold }
  from '@google-cloud/vertexai'

const vertex = new VertexAI({
  project:  process.env.GCLOUD_PROJECT!,
  location: 'us-central1',
})

const model = vertex.getGenerativeModel({
  model: 'gemini-1.5-pro',
  safetySettings: [
    { category: HarmCategory.HARM_CATEGORY_HARASSMENT,
      threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE },
  ],
  generationConfig: {
    maxOutputTokens: 2048,
    temperature:     0.4,
    topP:            0.8,
  },
})

export class AiService {

  static async generateProductDescription(
    productName: string,
    category:    string,
  ): Promise<string> {
    const prompt = `Write a compelling 2-paragraph product
description for: ${productName} (Category: ${category}).
Tone: professional, benefit-focused. No markdown.`

    const result = await model.generateContent(prompt)
    return result.response.candidates?.[0]
      ?.content.parts[0].text ?? ''
  }

  // Streaming — for long responses
  static async* streamResponse(prompt: string) {
    const stream = await model.generateContentStream(prompt)
    for await (const chunk of stream.stream) {
      const text = chunk.candidates?.[0]
        ?.content.parts[0].text
      if (text) yield text
    }
  }

  // Multimodal — image + text
  static async analyzeProductImage(imageUrl: string) {
    const result = await model.generateContent([
      { inlineData: { mimeType: 'image/jpeg', data: imageUrl } },
      'Describe this product image for a marketplace listing.',
    ])
    return result.response.candidates?.[0]?.content.parts[0].text
  }
}
ai.functions.ts — streaming HTTP responseVertex AI · Cloud Run
// Streaming Gemini response via Cloud Run (Cloud Functions
// has 60s timeout — Cloud Run supports longer streaming)
import express from 'express'
import { AiService } from './ai.service'
import { requireAuth } from '../shared/auth.middleware'

export const streamAiResponse = async (
  req: express.Request,
  res: express.Response,
) => {
  const user = await requireAuth(req, res)
  if (!user) return

  const { prompt } = req.body
  if (!prompt?.trim()) {
    res.status(400).json({ error: 'Prompt required' }); return
  }

  // SSE streaming — client reads chunks as they arrive
  res.setHeader('Content-Type', 'text/event-stream')
  res.setHeader('Cache-Control', 'no-cache')
  res.setHeader('Connection', 'keep-alive')

  for await (const chunk of AiService.streamResponse(prompt)) {
    res.write(`data: ${JSON.stringify({ text: chunk })}\n\n`)
  }
  res.write('data: [DONE]\n\n')
  res.end()
}
shared/firebase-admin.ts — singleton initinfra
import { initializeApp, getApps } from 'firebase-admin/app'

// Initialize once — guard against double-init in warm instances
if (!getApps().length) {
  initializeApp()  // auto-configured in GCP — no credentials needed
}

// Export typed Firestore with converter helper
import { getFirestore } from 'firebase-admin/firestore'

const db = getFirestore()
db.settings({ ignoreUndefinedProperties: true })

export { db }

// Typed converter — eliminates manual casting
export function converter<T>() {
  return {
    toFirestore:   (data: T) => data as any,
    fromFirestore: (snap: any): T => snap.data() as T,
  }
}

// Usage: db.collection('products').withConverter(converter<Product>())
shared/logger.ts — Cloud Logginginfra
import { logger as functionsLogger } from 'firebase-functions'

// Firebase Functions logger writes structured JSON to
// Google Cloud Logging automatically — no setup needed
export const logger = {
  debug: (msg: string, meta: object = {}) =>
    functionsLogger.debug(msg, meta),
  info:  (msg: string, meta: object = {}) =>
    functionsLogger.info(msg, meta),
  warn:  (msg: string, meta: object = {}) =>
    functionsLogger.warn(msg, meta),
  error: (msg: string, meta: object = {}) =>
    functionsLogger.error(msg, meta),
}

// Correlation ID middleware — trace logs across functions
import { Request, Response, NextFunction } from 'express'
import { randomUUID } from 'crypto'

export function correlationMiddleware(
  req: Request, res: Response, next: NextFunction
) {
  const traceId = req.headers['x-cloud-trace-context'] as string
    ?? randomUUID()
  res.setHeader('x-trace-id', traceId)
  next()
}

// package.json scripts:
// "deploy":      "firebase deploy --only functions"
// "deploy:prod": "firebase use prod && firebase deploy"
// "emulate":     "firebase emulators:start"
// "logs":        "firebase functions:log"
shared/validate.ts — Zod guardinfra
import { z } from 'zod'
import type { Response } from 'express'

// Returns typed data or sends 422 and returns null
export function validate<T extends z.ZodType>(
  schema:  T,
  data:    unknown,
  res:     Response,
): z.infer<T> | null {
  const result = schema.safeParse(data)
  if (!result.success) {
    res.status(422).json({
      success: false,
      error:   'Validation failed',
      details: result.error.flatten(),
    })
    return null
  }
  return result.data
}

// Environment config — validated at startup
const EnvSchema = z.object({
  GCLOUD_PROJECT:   z.string(),
  SERVICE_ACCOUNT:  z.string(),
  ENVIRONMENT:      z.enum(['development', 'staging', 'production']),
})

export const env = EnvSchema.parse(process.env)

04 — Data Flows

Every Event Traced

Serverless systems are event graphs, not request pipelines. Every interaction triggers a chain of events across managed services.

Authenticated HTTP request → Firestore

Client request
Cloud LB + Armor
Cloud Function
verifyIdToken()
Service.method()
Firestore write

Firestore write → event cascade

Firestore doc created
onDocumentCreated trigger
Pub/Sub publish
onMessagePublished
FCM push notification

User registration flow

Firebase Auth signup
beforeUserCreated trigger
Create /users/{uid} doc
Set custom claims { role }
JWT issued with RBAC

Image upload → AI processing

Client uploads to Storage
onObjectFinalized trigger
Vertex AI Vision analysis
Update Firestore doc
onSnapshot → client UI

Delayed task — Cloud Tasks

Order placed
scheduleTask(delay: 3600s)
Cloud Tasks queue
→ 1hr →
HTTP handler → reminder email

Realtime client subscription (no polling)

Client SDK onSnapshot()
Firestore persistent connection
Any write to that doc
Push delta to all listeners
UI updates automatically

Gemini streaming response

POST /ai/stream
Auth verify
Cloud Run handler
Vertex AI stream()
SSE chunks → client

05 — Feature Matrix

Every Requirement Mapped

All backend requirements from the original brief — translated to serverless GCP + Firebase equivalents.

🔑
Authentication + RBAC
Firebase Auth · Custom Claims
Firebase Auth replaces your entire JWT service. beforeUserCreated trigger sets custom claims (role) in the JWT. requireAuth() verifies + decodes in every function. Role checks on request.auth.token.role in Security Rules.
setCustomUserClaims()verifyIdToken()
🔐
Secrets management
Secret Manager
All secrets in Secret Manager — Stripe keys, SendGrid API keys, encryption keys. Functions access via secrets: ['SECRET_NAME'] binding (env var) or SDK call. IAM controls which service accounts can access which secrets.
runWith({secrets:[]})
Encryption at rest
GCP CMEK · AES-256
Firestore, Cloud Storage, and Pub/Sub are encrypted at rest with AES-256 by default. For field-level encryption of sensitive data (PII, payment info), use CMEK (Customer-Managed Encryption Keys) via Cloud KMS.
Cloud KMSCMEK
📨
Async task processing
Pub/Sub · Cloud Tasks
Pub/Sub for fire-and-forget events (at-least-once). Cloud Tasks for scheduled delays, deduplication, and rate-limited HTTP delivery. Both replace Celery + Redis worker queues entirely.
publishEvent()scheduleTask()
🔥
Real-time communication
Firestore onSnapshot · RTDB
Firestore's onSnapshot() listener gives real-time updates to all connected clients when any document changes — no WebSocket server to manage. Firebase Realtime DB for presence, cursors, typing indicators at sub-10ms latency.
onSnapshot()onValue()
🏃
WebSocket / streaming
Cloud Run
Cloud Functions have a 60-second timeout and no persistent connections. WebSocket servers and long-running streaming responses (Gemini SSE) run on Cloud Run — containerized, auto-scaling, same VPC.
Cloud Run service
🛡️
Rate limiting + DDoS
Cloud Armor · API Gateway
Cloud Armor WAF rules at the Global Load Balancer — rate limiting, IP blocking, geographic restrictions, OWASP Top 10 protections. All before traffic reaches your functions. Zero application code needed.
WAF security policy
📋
Security Rules
Firestore + Storage Rules
Database-level access control that cannot be bypassed. Rules run on Google's servers — even if someone calls the Firestore SDK directly with a valid token, rules still apply. Eliminates entire classes of authorization bugs.
firestore.rules
🧠
AI / ML integration
Vertex AI · Gemini
Gemini 1.5 Pro/Flash via Vertex AI SDK — text, multimodal, streaming. No API key needed inside GCP — uses ADC (Application Default Credentials). Vertex AI also hosts custom models and provides vector search.
generateContent()
⏱️
Scheduled jobs (cron)
Cloud Scheduler · onSchedule
onSchedule() in Cloud Functions v2 — wraps Cloud Scheduler. Supports cron syntax and human-readable intervals. Runs in your region, uses your service account. Logs to Cloud Logging automatically.
onSchedule('every 6 hours')
📊
Observability
Cloud Logging · Monitoring · Trace
Functions logger writes structured JSON to Cloud Logging. Cloud Monitoring provides dashboards, uptime checks, alerting. Cloud Trace shows latency breakdowns across function calls. Error Reporting groups and alerts on exceptions.
logger.info()Cloud Monitoring
🧪
Local development
Firebase Emulator Suite
Firebase Emulator Suite — local Firestore, Auth, Functions, Pub/Sub, Storage all running on localhost. Tests run against emulators — no cloud costs, no test data pollution. Rules are tested locally too.
firebase emulators:start

06 — SDK Registry

The npm Registry

Every package you need to build a complete GCP + Firebase backend. Pinned to production-stable versions.

Node.js version: Cloud Functions v2 supports Node.js 20 (recommended) and 22. Set "engines": {"node": "20"} in package.json. Always specify the runtime explicitly — default may change.
PackageVersionPurposeLayer
firebase-admin^12.xAdmin SDK — Firestore, Auth, Storage, FCM, Messaging server-side accessFirebase
firebase-functions^6.xCloud Functions v2 — onRequest, onCall, onDocumentCreated, onSchedule, onMessagePublishedFunctions
@google-cloud/pubsub^4.xPub/Sub publisher — create topics, publish messages, manage subscriptionsGCP
@google-cloud/tasks^5.xCloud Tasks — create HTTP tasks with delay, dedup, rate limitingGCP
@google-cloud/secret-manager^5.xSecret Manager SDK — access versioned secrets at runtimeSecurity
@google-cloud/vertexai^1.xVertex AI + Gemini API — text, multimodal, streaming, embeddingsAI
@google-cloud/storage^7.xCloud Storage — signed URLs, bucket management, object operationsGCP
@google-cloud/bigquery^7.xBigQuery — insert rows, run queries, stream events to analytics warehouseGCP
zod^3.23Runtime schema validation — request body, env vars, Firestore data shapesShared
express^4.21HTTP server for Cloud Run services — WebSocket, streaming AI responses, batch APIsHTTP
firebase-tools^13.xFirebase CLI — deploy functions, manage emulators, rules deployment (dev dep)dev
typescript^5.6TypeScript compiler — strict mode, ESM output targeting Node 20dev
tsx^4.xTypeScript execution — run .ts files directly for scripts and migrationsdev
vitest^2.xFast unit testing — test services, repositories, validators against emulatorstest
@firebase/rules-unit-testing^3.xSecurity Rules testing library — test firestore.rules against emulator with typed assertionstest
REPLACED BY GCP MANAGED SERVICES: JWT library → Firebase Auth · Redis → Firestore/RTDB · Celery/RabbitMQ → Pub/Sub + Cloud Tasks · Nginx → Cloud Load Balancer · Let's Encrypt → Managed TLS · Database server → Firestore · Cron daemon → Cloud Scheduler

✦ — AI Scaffold

Generate with AI

Paste into Claude, ChatGPT, Gemini, or any AI agent to scaffold this exact architecture from scratch.

 Architecture Prompt
You are a senior GCP + Firebase architect. Scaffold a production-ready full-stack app.

Stack:
- Frontend: Next.js 14 (App Router) on Firebase Hosting
- Auth: Firebase Authentication (Email, Google, Apple)
- Database: Cloud Firestore (NoSQL) + Firebase Realtime Database for presence
- Functions: Cloud Functions v2 (Node.js 20 / TypeScript) triggered by Firestore/HTTP/PubSub
- Storage: Cloud Storage for Firebase
- Messaging: Cloud Pub/Sub for async processing
- Scheduler: Cloud Scheduler → Pub/Sub → Cloud Functions
- AI: Vertex AI (Gemini Pro) via Firebase Extensions
- Monitoring: Cloud Logging + Cloud Trace + Error Reporting
- IaC: Firebase CLI + Terraform for GCP resources

Provide:
1. Firebase project structure (functions/, hosting/, firestore.rules, storage.rules)
2. Firestore security rules for multi-tenant auth
3. Cloud Function patterns (onDocumentCreated, callable, HTTP)
4. Next.js Firebase SDK setup (client + admin SDK)
5. Pub/Sub topic + Cloud Function subscription pattern
6. Firestore composite index definitions
7. GitHub Actions deploy pipeline (functions + hosting)
8. Vertex AI Gemini integration via Cloud Function
crafted with by Sam
 Copied to clipboard!