Connecting Firebase to Your App
Connecting Firebase to Your App
You have a Firebase project and understand Firestore. Now let's wire it all up — connecting your frontend and backend code to Firebase so they can read and write data.
Frontend Setup (React + TypeScript)
Step 1: Install the Firebase SDK
npm install firebase
Step 2: Set Up Environment Variables
Never hardcode your Firebase config. Use environment variables instead.
Create a .env file in your project root (or .env.local for Vite/Next.js):
# For Vite projects (variables must start with VITE_) VITE_FIREBASE_API_KEY=AIzaSyD... VITE_FIREBASE_AUTH_DOMAIN=my-app.firebaseapp.com VITE_FIREBASE_PROJECT_ID=my-app VITE_FIREBASE_STORAGE_BUCKET=my-app.firebasestorage.app VITE_FIREBASE_MESSAGING_SENDER_ID=123456789 VITE_FIREBASE_APP_ID=1:123456789:web:abc123
# For Next.js projects (variables must start with NEXT_PUBLIC_) NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyD... NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=my-app.firebaseapp.com # ... same pattern
Important: Add .env to your .gitignore so secrets aren't committed to Git!
Also create a .env.example file (without real values) so others know what variables are needed:
VITE_FIREBASE_API_KEY=your-api-key-here VITE_FIREBASE_AUTH_DOMAIN=your-app.firebaseapp.com VITE_FIREBASE_PROJECT_ID=your-project-id VITE_FIREBASE_STORAGE_BUCKET=your-app.firebasestorage.app VITE_FIREBASE_MESSAGING_SENDER_ID=your-sender-id VITE_FIREBASE_APP_ID=your-app-id
What to ask your AI: "Set up environment variables for Firebase in my [Vite/Next.js/Create React App] project. Include a .env.example file."
Step 3: Create the Firebase Config File
Create src/lib/firebase.ts (or src/config/firebase.ts):
import { initializeApp } from "firebase/app"; import { getAuth, GoogleAuthProvider } from "firebase/auth"; import { getFirestore } from "firebase/firestore"; import { getStorage } from "firebase/storage"; const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_API_KEY, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, appId: import.meta.env.VITE_FIREBASE_APP_ID, }; // Initialize Firebase const app = initializeApp(firebaseConfig); // Export the services you need export const auth = getAuth(app); export const googleProvider = new GoogleAuthProvider(); export const db = getFirestore(app); export const storage = getStorage(app);
For Next.js, replace import.meta.env.VITE_ with process.env.NEXT_PUBLIC_.
This file is the single source of truth for Firebase in your app. Every other file imports from here.
Step 4: Create Service Files
Organize your Firestore operations in service files:
// src/services/userService.ts import { db } from "@/lib/firebase"; import { collection, doc, getDocs, getDoc, addDoc, updateDoc, deleteDoc, query, where, orderBy, Timestamp } from "firebase/firestore"; // Define the interface interface User { id: string; name: string; email: string; role: string; createdAt: Timestamp; updatedAt: Timestamp; } type UserInput = Omit<User, "id" | "createdAt" | "updatedAt">; const COLLECTION = "users"; // Get all users export async function getUsers(): Promise<User[]> { const snapshot = await getDocs( query(collection(db, COLLECTION), orderBy("createdAt", "desc")) ); return snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as User)); } // Get user by ID export async function getUserById(id: string): Promise<User | null> { const docSnap = await getDoc(doc(db, COLLECTION, id)); if (!docSnap.exists()) return null; return { id: docSnap.id, ...docSnap.data() } as User; } // Create user export async function createUser(data: UserInput): Promise<string> { const docRef = await addDoc(collection(db, COLLECTION), { ...data, createdAt: Timestamp.now(), updatedAt: Timestamp.now(), }); return docRef.id; } // Update user export async function updateUser(id: string, data: Partial<UserInput>): Promise<void> { await updateDoc(doc(db, COLLECTION, id), { ...data, updatedAt: Timestamp.now(), }); } // Delete user export async function deleteUser(id: string): Promise<void> { await deleteDoc(doc(db, COLLECTION, id)); }
What to ask your AI: "Create a Firestore service file for my [collection] with full CRUD operations, TypeScript types, and proper timestamps."
Step 5: Use in React Components
import { useEffect, useState } from "react"; import { getUsers } from "@/services/userService"; import type { User } from "@/services/userService"; const UserList = () => { const [users, setUsers] = useState<User[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchUsers = async () => { const data = await getUsers(); setUsers(data); setLoading(false); }; fetchUsers(); }, []); if (loading) return <p>Loading...</p>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name} — {user.email}</li> ))} </ul> ); };
Backend Setup (Node.js + Firebase Admin)
For server-side code (scripts, APIs, Cloud Functions), you use firebase-admin instead of firebase.
Step 1: Install Firebase Admin
npm install firebase-admin
Step 2: Initialize with Credentials
Option A: Application Default Credentials (simplest for local development)
If you're logged in via firebase login or gcloud auth application-default login:
import { initializeApp, applicationDefault } from "firebase-admin/app"; import { getFirestore } from "firebase-admin/firestore"; initializeApp({ credential: applicationDefault(), projectId: "your-project-id", }); const db = getFirestore();
Option B: Service Account Key (for production servers)
- In Firebase Console → Project Settings → Service Accounts → Generate New Private Key
- Save the JSON file securely (never commit it to Git!)
import { initializeApp, cert } from "firebase-admin/app"; import { getFirestore } from "firebase-admin/firestore"; initializeApp({ credential: cert("./service-account-key.json"), }); const db = getFirestore();
Step 3: Use Firestore from the Backend
The Admin SDK syntax is slightly different from the client SDK:
import { getFirestore, Timestamp } from "firebase-admin/firestore"; const db = getFirestore(); // Create await db.collection("users").add({ name: "Alice", email: "alice@example.com", createdAt: Timestamp.now(), }); // Read const snapshot = await db.collection("users").get(); snapshot.forEach(doc => { console.log(doc.id, doc.data()); }); // Query const admins = await db.collection("users") .where("role", "==", "admin") .orderBy("createdAt") .get(); // Update await db.collection("users").doc("alice123").update({ role: "admin", updatedAt: Timestamp.now(), }); // Delete await db.collection("users").doc("alice123").delete();
Client SDK vs Admin SDK
Client (firebase) | Admin (firebase-admin) | |
|---|---|---|
| Runs in | Browser | Server (Node.js) |
| Auth | User's permissions | Full access (bypasses rules) |
| Syntax | addDoc(collection(db, "users"), data) | db.collection("users").add(data) |
| Import | from "firebase/firestore" | from "firebase-admin/firestore" |
| Use for | React apps, frontends | Scripts, APIs, Cloud Functions |
What to ask your AI: "I need to write a Node.js script that reads data from Firestore and generates a report. Use firebase-admin."
Common Patterns
Authentication Context (React)
// src/contexts/AuthContext.tsx import { createContext, useContext, useEffect, useState } from "react"; import { User, onAuthStateChanged } from "firebase/auth"; import { auth } from "@/lib/firebase"; interface AuthContextType { user: User | null; loading: boolean; } const AuthContext = createContext<AuthContextType>({ user: null, loading: true }); export const AuthProvider = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setUser(user); setLoading(false); }); return unsubscribe; }, []); return ( <AuthContext.Provider value={{ user, loading }}> {children} </AuthContext.Provider> ); }; export const useAuth = () => useContext(AuthContext);
Protected Routes
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { const { user, loading } = useAuth(); if (loading) return <p>Loading...</p>; if (!user) return <Navigate to="/login" />; return children; };
What to ask your AI: "Set up Firebase Authentication in my React app with Google sign-in, auth context, and protected routes."
Project Structure
A well-organized Firebase project typically looks like:
src/
├── lib/
│ └── firebase.ts # Firebase initialization
├── services/
│ ├── userService.ts # User CRUD operations
│ ├── postService.ts # Post CRUD operations
│ └── storageService.ts # File upload operations
├── contexts/
│ └── AuthContext.tsx # Auth state management
├── components/
│ └── ...
├── pages/
│ └── ...
└── types/
└── index.ts # Shared TypeScript interfaces
What's Next?
Your app is connected to Firebase! Next, we'll learn how to deploy your app to the internet with Firebase Hosting.
What to ask your AI: "Review my Firebase setup and project structure. Is there anything I should improve for production readiness?"