tool

Firebase

created 2026-05-10 backend · firestore · auth · storage · google-cloud · baas

Firebase

Google’s managed BaaS — Firestore (document DB), Auth, Storage, Functions, Hosting. Two SDKs: Web SDK (client, public-rules-gated) and Admin SDK (server, full-trust). The user’s default backend is Supabase, but Firebase has its place; this page captures when and how.

When Firebase wins

  • Real-time out of the boxonSnapshot() is one line; Supabase realtime is a separate channel setup
  • Google identity — Google sign-in is trivial; Apple/email/phone too
  • Storage rules in plain language — security rules DSL is more ergonomic than Postgres RLS for “user X owns object Y”
  • No Postgres maintenance — fully managed, no migrations, schema-on-read
  • Wide language SDKs — official Admin SDKs for Node, Go, Python, Java, .NET

When Firebase loses

  • Schema-on-read is its sin: no migrations, no relational integrity, no joins. You denormalise or you die.
  • Firestore queries are weak — single-field where + range only, compound indexes per query shape, no OR until recently and still awkward
  • Cost shape — pricing is per-document-read; chatty UIs surprise you
  • Vendor lock-in — moving off Firestore is a rewrite; moving off Postgres is a migration script
  • No SQL — no ad-hoc queries, no BI tooling without exporting to BigQuery

Drizzle ORM + Supabase is the user’s default for new projects for these reasons.

Server vs client SDKs

// Server (Admin SDK) — full trust, runs on the server only
import { initializeApp, cert } from 'firebase-admin/app'
import { getFirestore } from 'firebase-admin/firestore'

initializeApp({ credential: cert(serviceAccountJson) })
const db = getFirestore()  // bypasses all security rules
// Client (Web SDK) — security-rules-gated, runs in the browser
import { initializeApp } from 'firebase/app'
import { getFirestore } from 'firebase/firestore'

initializeApp({ apiKey, authDomain, projectId, ... })  // public config
const db = getFirestore()  // every read/write checked by security rules

Two import paths, two db types — same shape but different methods. Easy to confuse. The Web SDK uses functional API (collection(db, 'orders')) while Admin SDK uses method chains (db.collection('orders')).

Auth — session cookies are the right pattern

Storing the Firebase ID token in localStorage works but is the wrong default in modern Next.js:

  1. ID token is exposed to JS → XSS risk
  2. Server can’t read it without a request round-trip
  3. Token expiry is 1 hour, refresh dance is finicky

Better: trade the ID token for a server-managed session cookie via Admin SDK:

// Server Action: client posts ID token, server returns Set-Cookie
const sessionCookie = await auth.createSessionCookie(idToken, { expiresIn: 5 * 24 * 3600 * 1000 })
cookies().set('session', sessionCookie, { httpOnly: true, secure: true, sameSite: 'lax' })
// Middleware: verify on every request
const decoded = await auth.verifySessionCookie(cookie, true)

Used in kulevents — admin login flow.

Env-aware Firestore collections

Cheap dev/prod split without separate Firebase projects:

const env = process.env.FIREBASE_ENV ?? 'development'
const suffix = env === 'production' ? '' : '-dev'
export const COLLECTIONS = {
  ORDERS: `orders${suffix}`,
  LAYOUTS: `layouts${suffix}`,
  // ...
}

A single project hosts both, queries pick the right collection name, security rules cover both. Used in kulevents (lib/firebase/config.ts). Fine for a single-developer project; for proper isolation use separate Firebase projects.

Used in

  • kulevents — Firestore (orders, layouts, templates), Storage (template images, custom-design results), Auth (admin only). First Firebase project in the kulify ecosystem.

See also

  • Supabase — user’s preferred backend (Postgres + Auth + Storage + Realtime + Edge Functions)
  • Drizzle ORM — preferred ORM when on Postgres
  • kulevents — production usage