Chapter 01 · Firestore Foundations, Firebase/Google Cloud, Editions, Modes, Locations, and Lab Setup
Build a Reproducible Lab with Authentication, Rules, Test Data, Index Configuration, Logging, and Safe Cleanup
Turn Chapter 01 into a reusable Firestore lab contract with Authentication, restrictive Rules, deterministic data, index configuration, client/server verification, logging, evidence capture, and safe reset automation.
Learning outcomes
A useful course lab is not a one-time demo. AtlasMart needs a harness that every later chapter can start, verify, break safely, and reset without touching production or carrying hidden state from the previous exercise.
Organize the Chapter 01 files into a repeatable emulator-backed lab with stable project, ports, schema and seed data.
Verify client authentication, Security Rules allow/deny cases and privileged server behavior with explicit assertions.
Record index definitions, logs and environment identity so later query failures are diagnosable.
Define safe reset and optional emulator import/export workflows that cannot delete unrelated cloud data.
Produce a verification manifest that states what the local lab proves and which production properties remain unverified.
The reproducible lab pins Firebase CLI 15.30.0, Firebase JavaScript SDK 12.19.0, Firebase Admin Node.js SDK 14.4.0, and Node.js 22 or newer for the Admin SDK. These are a dated lab baseline, not permanent course guarantees. Firestore service capabilities, editions, pricing, locations, emulator fidelity and SDK support continue to evolve; re-check the official documentation before using the commands later.
The generation environment used to build this chapter does not run Firebase Emulator Suite or a billed Google Cloud project. Commands and API shapes were checked against current official documentation, but expected output is described by invariant and evidence shape rather than fabricated as captured output. The mandatory lab uses a Firebase demo project ID and local emulators. Optional production verification must use an isolated test project with explicit billing awareness.
1. The reusable lab contract
The lab should answer five questions on every run: Which project/database surface am I using? Which tool versions are running? Which seed documents exist? Which requests are allowed/denied? What state will cleanup remove? If any answer is implicit, later chapters inherit a hidden dependency.
AtlasMart standardizes on project ID
demo-atlasmart-firestore, default emulator
database, Firestore port 8080, Auth port 9099 and UI port 4000.
Product IDs and schema fields are stable so Chapter 02 can
extend the model instead of inventing a new dataset.
| Contract item | Chapter 01 value | Reason |
|---|---|---|
| Project ID | demo-atlasmart-firestore | No live cloud backing; safer mandatory lab |
| Database | (default) emulator database | Simple shared Core baseline |
| Products | products/p-1001, products/p-1002 | Stable catalog fixtures |
| Profiles | profiles/{uid} | Authentication/Rules boundary |
| Auth | Anonymous user in Auth emulator | Deterministic untrusted client identity |
| Server path | Admin SDK to Firestore emulator | Trusted path without production key |
2. Repository-local lab layout
atlasmart-firestore-lab/├─ package.json├─ firebase.json├─ firestore.rules├─ firestore.indexes.json├─ scripts/│ ├─ seed.mjs│ ├─ client-check.mjs│ ├─ server-check.mjs│ └─ verify.mjs└─ .gitignore
Keep configuration files in source control. Keep emulator exports, logs and any real credential files out of source control. The course itself never requires a downloaded service-account JSON key.
.emulator-data/*.log.env.env.*service-account*.jsonapplication_default_credentials.json
3. Assemble the deterministic configuration
{ "name": "atlasmart-firestore-lab", "private": true, "type": "module", "engines": { "node": ">=22" }, "dependencies": { "firebase": "12.19.0", "firebase-admin": "14.4.0" }, "devDependencies": { "firebase-tools": "15.30.0" }, "scripts": { "emulators": "firebase emulators:start --project demo-atlasmart-firestore --only auth,firestore", "seed": "node scripts/seed.mjs", "client-check": "node scripts/client-check.mjs", "server-check": "node scripts/server-check.mjs" }}
{ "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "emulators": { "auth": { "port": 9099 }, "firestore": { "port": 8080 }, "ui": { "enabled": true, "port": 4000 }, "singleProjectMode": true }}
rules_version = '2';service cloud.firestore { match /databases/{database}/documents { match /products/{productId} { allow read: if true; allow write: if false; } match /profiles/{uid} { allow read, create, update: if request.auth != null && request.auth.uid == uid; allow delete: if false; } match /{document=**} { allow read, write: if false; } }}
{ "indexes": [], "fieldOverrides": []}
Even an empty index configuration is meaningful: it records that Chapter 01 does not depend on a composite index. Chapter 06 will add index definitions deliberately and measure the consequences.
4. Seed state and verify server-side invariants
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";process.env.GCLOUD_PROJECT = "demo-atlasmart-firestore";import { initializeApp } from "firebase-admin/app";import { getFirestore } from "firebase-admin/firestore";initializeApp({ projectId: "demo-atlasmart-firestore" });const db = getFirestore();await db.doc("products/p-1001").set({ name: "Trail Camera", category: "cameras", price: 129.90, public: true, schemaVersion: 1});await db.doc("products/p-1002").set({ name: "USB-C Hub", category: "accessories", price: 49.50, public: true, schemaVersion: 1});console.log("seeded AtlasMart emulator documents");process.exit(0);
Run the seed after startup. The expected invariant is exactly two base catalog products plus any temporary documents created by the verification scripts. A later lesson that changes product shape should either migrate these fixtures or update the contract explicitly.
5. Verify client security as executable behavior
process.env.FIREBASE_AUTH_EMULATOR_HOST = "127.0.0.1:9099";process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";import { initializeApp } from "firebase/app";import { getFirestore, connectFirestoreEmulator, doc, getDoc, setDoc} from "firebase/firestore";import { getAuth, connectAuthEmulator, signInAnonymously} from "firebase/auth";const app = initializeApp({ apiKey: "demo-key", projectId: "demo-atlasmart-firestore", appId: "1:123:web:demo"});const db = getFirestore(app);const auth = getAuth(app);connectFirestoreEmulator(db, "127.0.0.1", 8080);connectAuthEmulator(auth, "http://127.0.0.1:9099", { disableWarnings: true });const credential = await signInAnonymously(auth);const uid = credential.user.uid;const product = await getDoc(doc(db, "products", "p-1001"));console.log("public product readable:", product.exists(), product.data()?.name);await setDoc(doc(db, "profiles", uid), { displayName: "AtlasMart learner", schemaVersion: 1});console.log("own profile write allowed:", uid);try { await setDoc(doc(db, "products", "p-client-write"), { name: "forbidden" }); throw new Error("unexpected client product write success");} catch (error) { console.log("client product write denied as intended:", error.code ?? error.message);}
Security verification must contain both positive and negative cases. “The app worked” is not evidence that access control worked. The forbidden write is as important as the successful read.
6. Verify trusted server behavior without pretending Rules apply
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";process.env.GCLOUD_PROJECT = "demo-atlasmart-firestore";import { initializeApp } from "firebase-admin/app";import { getFirestore } from "firebase-admin/firestore";initializeApp({ projectId: "demo-atlasmart-firestore" });const db = getFirestore();await db.doc("products/p-server-write").set({ name: "Trusted backend fixture", category: "lab", price: 1, schemaVersion: 1});const snap = await db.doc("products/p-server-write").get();console.log("trusted server write/read:", snap.exists, snap.data()?.name);
The trusted server test establishes the separation that many Firebase applications miss: client access is Rules-controlled, server access is IAM/application-controlled. Later IAM chapters will replace “trusted” as a vague label with least-privilege roles and workload identity.
7. Add a single verification runner
A small orchestration script can make the acceptance criteria binary. It should fail the run on unexpected success/failure rather than require a learner to inspect console output manually.
import { spawn } from "node:child_process";const commands = [ ["node", ["scripts/seed.mjs"]], ["node", ["scripts/client-check.mjs"]], ["node", ["scripts/server-check.mjs"]]];for (const [cmd, args] of commands) { const code = await new Promise((resolve) => { const child = spawn(cmd, args, { stdio: "inherit", env: process.env }); child.on("exit", resolve); }); if (code !== 0) process.exit(code ?? 1);}console.log("Chapter 01 verification complete");
Run this from a terminal where the emulator endpoint environment is set for server scripts, or keep those variables inside the scripts as Chapter 01 does. Later CI lessons centralize environment injection.
8. Logging and evidence capture: record facts, not screenshots
Useful evidence includes version output, startup logs, project ID, emulator ports, successful/denied operation assertions, and the final document list. Screenshots can supplement the record but should not be the only proof.
node --versionnpx firebase --versionnpm ls firebase firebase-admin firebase-toolsnpm run seednpm run client-checknpm run server-check# Optional: use Emulator UI only to inspect the same state visually.# Firestore: http://127.0.0.1:4000/firestore# Authentication: http://127.0.0.1:4000/auth
Do not paste real tokens, credential paths or service-account contents into course logs. If a later cloud exercise uses ADC, record the principal identity—not the secret material.
9. Failure injection: make the wrong state safe and reversible
Chapter 01 has three useful failures that require no destructive cloud action:
-
Comment out
connectFirestoreEmulatorwhile using a demo project ID. The operation should fail rather than mutate a real cloud project. -
Change the client write target from
profiles/{uid}toprofiles/someone-else. Rules should deny it. - Stop the Firestore emulator while the client runs. The application should surface an availability/network failure instead of silently claiming success.
After each failure, restore the known configuration and rerun the verification runner. Failure injection is complete only when recovery is verified.
10. Safe reset and optional state export
# Default reset: stop with Ctrl+C and restart.# No production data is involved.# Persistent local state for a later exercise:mkdir -p .emulator-datanpx firebase emulators:start \ --project demo-atlasmart-firestore \ --only auth,firestore \ --import=.emulator-data \ --export-on-exit=.emulator-data# To reset exported state:# 1. stop emulators# 2. remove only the repository-local .emulator-data directory# 3. restart and run the seed again
Never turn cleanup into a generic
firebase firestore:delete or cloud-recursive-delete
command in Chapter 01. The course will teach destructive
operations later with explicit blast-radius controls.
11. Acceptance checklist and production non-guarantees
- Node.js reports 22 or newer; CLI/SDK package versions match the pinned baseline.
- Firestore/Auth emulator startup identifies the demo project and expected ports.
- Seed creates deterministic product fixtures.
- Client public product read succeeds.
- Authenticated client can write only its own profile.
- Client product write fails as expected.
- Admin/server write succeeds, demonstrating the separate trusted path.
- Restart/reset returns the lab to the same known state.
Passing all checks still does not prove production IAM, App Check, region placement, SLA, real billing, production composite-index behavior, Enterprise query execution, network latency, contention, backup/recovery or production-scale performance. Those are future chapter acceptance criteria.
12. Production judgment and bridge to Chapter 02
The best outcome from Chapter 01 is not “we know Firebase.” It is a reproducible boundary around what has been proven. AtlasMart now has a safe client/server trust model, deterministic fixtures, a resettable emulator environment and a version record. This is enough foundation to discuss document paths, field types, maps, arrays, references, timestamps, GeoPoints and platform limits without environment ambiguity.
Chapter 02 will keep these project and collection names stable while expanding the document model and inspecting serialization/limit behavior across client and server SDKs.
Knowledge check
Check your understanding
- Why should an empty `firestore.indexes.json` still be committed in Chapter 01?
- What two kinds of Security Rules tests are necessary for meaningful authorization evidence?
- Why is the Admin SDK allowed to bypass the Rules file in this lab?
- What is the safest default reset for the Chapter 01 emulator lab?
- List three important production properties this lab intentionally does not prove.
Review the answers
1. It records the intended index baseline and makes future index changes reviewable. Later chapters can show exactly when and why composite/vector/index exemptions are introduced.
2. Positive allow cases and negative deny cases. Testing only successful operations cannot reveal overbroad authorization.
3. Admin/server libraries are privileged and use IAM/Google credentials in production; Firestore Security Rules apply to mobile/web client paths, not server libraries. The lab demonstrates that architectural boundary.
4. Stop the local emulators and restart them, then rerun deterministic seed scripts. If exported emulator state is used, delete only the repository-local export directory.
5. Examples include production location, billing/SKU behavior, cloud IAM, App Check enforcement, production index enforcement, Enterprise/Pipeline behavior, real latency/SLA, backup recovery and production contention.
Summary and next step
Chapter 01 keeps Firestore claims tied to an observable state surface: client versus server, emulator versus production, project versus database, and Standard versus Enterprise/mode. Preserve the lab evidence and do not convert unknown production facts into assumptions.
Next, continue to Document/Collection Hierarchy, Paths, IDs, Subcollections, and the Absence of Traditional Server-Side Joins in Core Operations.
Authoritative references
- Cloud Firestore documentation — Official product documentation entry point.
- Firestore editions overview — Current Standard/Enterprise feature and indexing distinctions.
- Firestore Enterprise edition modes — Native Core/Pipeline and MongoDB compatibility mode boundaries.
- Firestore in Native mode / Pipeline operations — Current Enterprise Native operation model.
- Firestore security overview — Client Security Rules/App Check versus server IAM trust paths.
- Connect to the Firestore Emulator — Emulator connection guidance and documented differences from production.
- Firebase release notes — Current Firebase CLI and SDK versions.
- Firebase Local Emulator Suite — Official local development/test suite.
- Use the Emulator Suite in CI — Configuration and execution patterns.
- Firestore Rules test guidance — Authorization testing with the emulator.
- Firestore pricing — Production billing model that the emulator does not prove.