Zum Inhalt springen
Zomer Gregorio

Zomer Gregorio

Softwareentwickler

Lebenslauf
Frontend
  • TypeScript
  • React
  • Next.js
  • TanStack
  • Tailwind CSS
Backend
  • Node.js
  • Hono
  • Express
  • Django
Daten
  • PostgreSQL
  • Redis
  • Drizzle
  • Prisma
Infrastruktur / Tools
  • Docker
  • GitHub Actions
  • Cloudflare
  • Vercel
Blog

Full-Stack Fusion: React, Fastify, & Firebase with TypeScript

· react · fastify · firebase · typescript · fullstack

Discover how to build robust, scalable web applications by seamlessly integrating React, Fastify, and Firebase using TypeScript.

Auf dem Laufenden bleiben

Erhalte eine kurze Nachricht, wenn ich etwas Neues veröffentliche. Deine E-Mail- oder Browserregistrierung wird nur für diese Updates gespeichert und kann jederzeit beendet werden. Ein Konto oder Trackingprofil ist nicht erforderlich.

Bevor Benachrichtigungen beginnen, ist eine Bestätigungs-E-Mail erforderlich.

Full-Stack Fusion: React, Fastify, & Firebase with TypeScript

Building modern web applications demands a stack that offers speed, scalability, and a superior developer experience. This post demonstrates how to integrate React for dynamic UIs, Fastify for a high-performance backend, and Firebase for backend services, all powered by TypeScript.

The Integrated Architecture

This architecture leverages React for the frontend, providing a component-based UI. Fastify serves as the API layer, handling business logic and secure interactions. Firebase provides authentication, real-time database (Firestore), and other essential services, minimizing infrastructure overhead.

Fastify Backend: High-Performance API

Fastify is a fast and low-overhead web framework for Node.js, ideal for building robust APIs. We'll use it to expose secure endpoints and interact with Firebase Admin SDK.

First, set up a basic Fastify server with TypeScript and install @fastify/cors to handle cross-origin requests from your React app. Install firebase-admin to interact with Firebase services securely from the backend.

// src/server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import admin from 'firebase-admin';
import * as dotenv from 'dotenv';
 
dotenv.config();
 
// Initialize Firebase Admin SDK (replace with your service account key path or config)
// Ensure FIREBASE_SERVICE_ACCOUNT_KEY is a stringified JSON object in your .env
const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY || '{}');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});
 
const fastify = Fastify({ logger: true });
 
fastify.register(cors, {
  origin: 'http://localhost:3000' // Your React app's origin
});
 
interface AuthenticatedRequest extends Fastify.FastifyRequest {
  user?: admin.auth.DecodedIdToken;
}
 
// Pre-handler hook for authentication
fastify.addHook('preHandler', async (request: AuthenticatedRequest, reply) => {
  try {
    const authHeader = request.headers.authorization;
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      throw new Error('No authorization token provided.');
    }
    const idToken = authHeader.split('Bearer ')[1];
    const decodedToken = await admin.auth().verifyIdToken(idToken);
    request.user = decodedToken;
  } catch (error: any) {
    reply.code(401).send({ message: error.message || 'Unauthorized' });
    throw new Error('Unauthorized'); // Stop further processing
  }
});
 
fastify.get('/api/secure-data', async (request: AuthenticatedRequest, reply) => {
  // Access authenticated user via request.user
  const userId = request.user?.uid;
  const userEmail = request.user?.email;
 
  // Example: Fetch data from Firestore
  const docRef = admin.firestore().collection('users').doc(userId);
  const doc = await docRef.get();
 
  if (doc.exists) {
    return { message: `Hello, ${userEmail}! Here's your data:`, data: doc.data() };
  } else {
    // Create a new user document if it doesn't exist
    await docRef.set({ email: userEmail, createdAt: admin.firestore.FieldValue.serverTimestamp() });
    return { message: `Welcome, ${userEmail}! New user data created.`, data: { email: userEmail } };
  }
});
 
const start = async () => {
  try {
    await fastify.listen({ port: 3001 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
 
start();

This Fastify server includes a pre-handler hook to verify Firebase ID tokens, securing your API endpoints. It then uses the Firebase Admin SDK to interact with Firestore, demonstrating how to fetch or create user-specific data.

React Frontend: Dynamic User Interface

React builds interactive user interfaces efficiently. We'll use the Firebase Client SDK for user authentication and fetch to communicate with our Fastify backend.

Install firebase and react-firebase-hooks (optional, but useful for auth state).

// src/firebaseConfig.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
 
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};
 
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
// src/App.tsx
import React, { useState, useEffect } from 'react';
import { signInWithPopup, GoogleAuthProvider, signOut, User } from 'firebase/auth';
import { auth } from './firebaseConfig';
 
function App() {
  const [user, setUser] = useState<User | null>(null);
  const [data, setData] = useState<any | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
 
  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((currentUser) => {
      setUser(currentUser);
      setData(null); // Clear data on auth state change
    });
    return () => unsubscribe();
  }, []);
 
  const handleGoogleSignIn = async () => {
    const provider = new GoogleAuthProvider();
    try {
      await signInWithPopup(auth, provider);
    } catch (err: any) {
      console.error(err);
      setError(err.message);
    }
  };
 
  const handleSignOut = async () => {
    try {
      await signOut(auth);
    } catch (err: any) {
      console.error(err);
      setError(err.message);
    }
  };
 
  const fetchSecureData = async () => {
    if (!user) return;
    setLoading(true);
    setError(null);
    try {
      const idToken = await user.getIdToken();
      const response = await fetch('http://localhost:3001/api/secure-data', {
        headers: {
          Authorization: `Bearer ${idToken}`,
        },
      });
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      setData(result);
    } catch (err: any) {
      console.error('Error fetching secure data:', err);
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>React + Fastify + Firebase App</h1>
      {!user ? (
        <button onClick={handleGoogleSignIn}>Sign in with Google</button>
      ) : (
        <div>
          <p>Welcome, {user.displayName || user.email}!</p>
          <button onClick={handleSignOut}>Sign Out</button>
          <button onClick={fetchSecureData} disabled={loading} style={{ marginLeft: '10px' }}>
            {loading ? 'Loading...' : 'Fetch Secure Data'}
          </button>
          {error && <p style={{ color: 'red' }}>Error: {error}</p>}
          {data && (
            <div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
              <h3>Secure Data from Fastify:</h3>
              <pre>{JSON.stringify(data, null, 2)}</pre>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
 
export default App;

This React component handles user authentication via Firebase's client SDK. Once authenticated, it retrieves the user's ID token and sends it to the Fastify backend to access a protected endpoint. The backend then verifies this token and returns user-specific data.

Firebase: Authentication & Data Persistence

Firebase provides scalable backend services without managing servers. Firebase Authentication handles user sign-up/sign-in, while Firestore offers a flexible, NoSQL cloud database.

  • Authentication: The React frontend uses firebase/auth for client-side authentication flows (e.g., Google Sign-In). The Fastify backend uses firebase-admin to verify the authenticity of user tokens, ensuring only legitimate requests access protected resources.
  • Firestore: Both client and server can interact with Firestore. The Fastify backend, with its elevated privileges via firebase-admin, can perform server-side data operations (e.g., creating/updating user profiles, sensitive data access). The React frontend can also directly access Firestore for less sensitive, user-specific data, often secured by Firestore Security Rules.

Conclusion

By integrating React, Fastify, and Firebase with TypeScript, you create a powerful, type-safe, and scalable full-stack application. React delivers a rich user experience, Fastify ensures a performant and secure API layer, and Firebase handles the heavy lifting of authentication and data management, allowing developers to focus on core application logic. This stack provides a robust foundation for modern web development, ready for complex features and deployment to platforms like Vercel (for React) and Cloud Run (for Fastify).