Guide for Firebase setup, Firestore operations, and client-side data management in TrainTrack
This skill provides comprehensive guidance for working with Firebase, including initialization, Firestore client SDK usage, security rules, and environment configuration.
Singleton Pattern: Firebase is initialized once using a singleton pattern to prevent multiple app instances.
Configuration File: src/lib/firebase.ts
Environment Variables Required:
NEXT_PUBLIC_FIREBASE_API_KEY=<key>
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<domain>
NEXT_PUBLIC_FIREBASE_PROJECT_ID=<project_id>
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=<bucket>
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=<sender_id>
NEXT_PUBLIC_FIREBASE_APP_ID=<app_id>
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=<measurement_id>
NEXT_PUBLIC_FIREBASE_DATABASE_ID=<database_id> # Optional
Note: NEXT_PUBLIC_ prefixed variables are exposed to the browser.
TrainTrack uses Client-Side Rendering (CSR) with direct Firestore access from the browser.
Collection Operations:
import { collection, getDocs, addDoc, query, where } from 'firebase/firestore';
// Get all documents
const snapshot = await getDocs(collection(db, 'bookings'));
// Query with filters
const q = query(collection(db, 'bookings'), where('status', '==', 'pending'));
const snapshot = await getDocs(q);
Document Operations:
import { doc, getDoc, updateDoc, deleteDoc } from 'firebase/firestore';
// Get single document
const docRef = doc(db, 'bookings', bookingId);
const docSnap = await getDoc(docRef);
// Update document
await updateDoc(docRef, { status: 'completed' });
// Delete document
await deleteDoc(docRef);
Real-time Updates:
import { onSnapshot } from 'firebase/firestore';
const unsubscribe = onSnapshot(collection(db, 'bookings'), (snapshot) => {
snapshot.forEach((doc) => {
console.log(doc.data());
});
});
// Clean up on unmount
unsubscribe();
bookings/)Stores main booking records with:
irctcAccounts/)Stores IRCTC credentials and wallet information:
bookingRecords/)Stores completion/payment records:
handlers/)Stores handler/agent names for assignment.
Critical Pattern: Firestore stores dates as Timestamp objects. Always convert to ISO strings for client compatibility:
// When reading from Firestore
journeyDate: booking.journeyDate?.toDate?.()?.toISOString() || booking.journeyDate
// When writing to Firestore (optional, Firestore handles Dates)
journeyDate: new Date(journeyDateString)
Custom Hooks Pattern: Combine Firestore client SDK with React Query for caching and automatic refetching.
Example Structure:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { collection, getDocs, addDoc, updateDoc } from 'firebase/firestore';
export function useBookings() {
return useQuery({
queryKey: ['bookings'],
queryFn: async () => {
const snapshot = await getDocs(collection(db, 'bookings'));
return snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
},
staleTime: 60000, // 60 seconds
});
}
export function useUpdateBooking() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, data }) => {
await updateDoc(doc(db, 'bookings', id), data);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['bookings'] });
},
});
}
Key Hooks: src/hooks/
Current Status: Rules allow unrestricted read/write for development.
Production Requirements: In production, add:
request.auth != null)Configuration File: firestore.rules
Example production rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /bookings/{booking} {
allow read: if request.auth != null;
allow write: if request.auth != null &&
request.auth.uid == resource.data.userId;
}
}
}
Project Config: .firebaserc
{
"projects": {
"default": "your-project-id"
}
}
Hosting Config: firebase.json
{
"hosting": {
"public": "out",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
Firestore Rules: firestore.rules
Installation: Dependencies in package.json
firebase - Firebase client SDK@tanstack/react-query - Data fetching and cachingInitialization: src/lib/firebase.ts
Usage Pattern:
import { db } from '@/lib/firebase';
import { collection, getDocs } from 'firebase/firestore';
const snapshot = await getDocs(collection(db, 'bookings'));
import { collection, addDoc } from 'firebase/firestore';
import { db } from '@/lib/firebase';
const docRef = await addDoc(collection(db, 'bookings'), {
source: 'Station A',
destination: 'Station B',
status: 'pending',
createdAt: new Date(),
});
import { doc, updateDoc } from 'firebase/firestore';
await updateDoc(doc(db, 'bookings', bookingId), {
status: 'completed',
updatedAt: new Date(),
});
import { collection, query, where, getDocs } from 'firebase/firestore';
const q = query(
collection(db, 'bookings'),
where('status', '==', 'pending'),
where('journeyDate', '>=', startDate)
);
const snapshot = await getDocs(q);
import { collection, onSnapshot } from 'firebase/firestore';
const unsubscribe = onSnapshot(
query(collection(db, 'bookings'), where('status', '==', 'pending')),
(snapshot) => {
const bookings = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Update state or invalidate React Query
}
);
Timestamp Conversion Errors:
.toDate().toISOString()Multiple App Instances:
Permission Denied Errors:
Real-time Updates Not Working: