Bootcamp
A 60-minute Firebase bootcamp that wires up Auth + Firestore + rules + App Check + a Cloud Function. The result is a tiny working app that meets the production-readiness bar.
A 60-minute Firebase bootcamp
EXAMPLE
# ===== Objectives =====
# 1. Create a project + enable Auth + Firestore
# 2. Ship security rules that pass the firestore-emulator tests
# 3. Add App Check to stop abuse
# 4. Add a Cloud Function that runs on Firestore writes
# 5. Ship a tiny web client that signs in, writes, listens
# ===== 0-5 min: create the project =====
# console.firebase.google.com -> Add Project (no analytics for the bootcamp).
# firebase login
# firebase init firestore functions hosting emulators
# Pick existing project; Functions in TypeScript; emulators for firestore, auth, functions.
# ===== 5-15 min: schema + rules =====
// firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{db}/documents {
match /notes/{id} {
allow read, update, delete: if request.auth != null
&& resource.data.uid == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.keys().hasOnly(['uid','body','createdAt'])
&& request.resource.data.uid == request.auth.uid
&& request.resource.data.body is string
&& request.resource.data.body.size() <= 2000;
}
}
}
// firestore.indexes.json
// {
// "indexes": [
// {
// "collectionGroup": "notes",
// "queryScope": "COLLECTION",
// "fields": [
// { "fieldPath": "uid", "order": "ASCENDING" },
// { "fieldPath": "createdAt", "order": "DESCENDING" }
// ]
// }
// ]
// }
# ===== 15-25 min: test the rules with the emulator =====
firebase emulators:start --only firestore
# In another terminal:
# rules.test.ts
import { initializeTestEnvironment, assertSucceeds, assertFails } from '@firebase/rules-unit-testing';
import { setDoc, doc, getDoc } from 'firebase/firestore';
const env = await initializeTestEnvironment({
projectId: 'shop-test',
firestore: { rules: require('fs').readFileSync('firestore.rules', 'utf8') },
});
const aliceDb = env.authenticatedContext('u1').firestore();
const bobDb = env.authenticatedContext('u2').firestore();
it('owner can read own note', async () => {
await env.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'notes/n1'),
{ uid: 'u1', body: 'hi', createdAt: Date.now() });
});
await assertSucceeds(getDoc(doc(aliceDb, 'notes/n1')));
await assertFails(getDoc(doc(bobDb, 'notes/n1')));
});
# ===== 25-35 min: App Check =====
// In console: App Check -> register
// - Web: reCAPTCHA Enterprise
// - Android: Play Integrity
// - iOS: DeviceCheck or App Attest
// Enforce on Firestore + Auth (console: API protections -> Enforce).
// In the client:
import { initializeApp } from 'firebase/app';
import { initializeAppCheck, ReCaptchaEnterpriseProvider } from 'firebase/app-check';
const app = initializeApp({ /* config */ });
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(process.env.RECAPTCHA_SITE_KEY!),
isTokenAutoRefreshEnabled: true,
});
# ===== 35-50 min: a Cloud Function =====
// functions/src/index.ts
import { onDocumentCreated } from 'firebase-functions/v2/firestore';
import { logger } from 'firebase-functions';
export const onNoteCreated = onDocumentCreated('notes/{id}', async (event) => {
const data = event.data?.data();
if (!data) return;
logger.info('note created', { id: event.params.id, uid: data.uid });
// do something cross-cutting: push notification, audit log, etc.
});
# Deploy
firebase deploy --only functions
# ===== 50-60 min: the client =====
import { getAuth, signInAnonymously, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, collection, addDoc, query, where, orderBy, onSnapshot, serverTimestamp } from 'firebase/firestore';
const auth = getAuth(app);
const db = getFirestore(app);
await signInAnonymously(auth);
onAuthStateChanged(auth, (user) => {
if (!user) return;
const ref = collection(db, 'notes');
// Subscribe to MY notes
const q = query(ref, where('uid', '==', user.uid), orderBy('createdAt', 'desc'));
onSnapshot(q, (snap) => snap.forEach((d) => console.log(d.id, d.data())));
// Create a note
document.getElementById('add')!.addEventListener('click', async () => {
await addDoc(ref, { uid: user.uid, body: 'hello', createdAt: serverTimestamp() });
});
});
# ===== Production-readiness checklist =====
# - Rules tested + checked into git
# - App Check enforced on Auth + Firestore
# - Cloud Functions have minInstances=1 on hot paths
# - Backups: scheduled Firestore export to a GCS bucket
# - Monitoring: Crashlytics on mobile, Sentry on web, Cloud Logging alerts
# - Per-user rate limits on writes that cost money
# ===== Pitfalls =====
# - 'allow read, write: if true' shipped from the tutorial
# - Cloud Function deployed without minInstances=1 -> 2-second cold starts
# - Storing big blobs in Firestore (use Storage)
# - Loading 10k docs into the client (paginate, with cursors)
# - No backups -> a bug or a malicious write loses data permanently
Why it matters
Lock down rules, enforce App Check, and run the firestore emulator tests in CI from day one. The cost of those three is a quiet afternoon; the cost of skipping them is the post-launch script that scrapes your Auth quota and the rule typo that opened your DB to the world.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…