Skip to main content

Introduction

While Refine doesn’t have an official Firebase data provider package, you can easily build applications with Firebase by creating a custom data provider or using community packages. This guide shows you how to integrate Firebase with Refine.

Installation

Install the Firebase SDK:
npm install firebase

Setup Firebase

1

Create Firebase project

Create a Firebase project in the Firebase Console.
2

Initialize Firebase

Create a Firebase configuration file:
// src/firebase.ts
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const auth = getAuth(app);
3

Create data provider

Create a custom data provider for Firebase:
// src/dataProvider.ts
import { DataProvider } from "@refinedev/core";
import {
  collection,
  doc,
  getDoc,
  getDocs,
  addDoc,
  updateDoc,
  deleteDoc,
  query,
  where,
  orderBy,
  limit,
  startAfter,
} from "firebase/firestore";
import { db } from "./firebase";

export const dataProvider: DataProvider = {
  getList: async ({ resource, pagination, filters, sorters }) => {
    const { current = 1, pageSize = 10 } = pagination ?? {};

    let q = collection(db, resource);
    let constraints: any[] = [];

    // Add filters
    if (filters) {
      filters.forEach((filter) => {
        if (filter.operator === "eq") {
          constraints.push(where(filter.field, "==", filter.value));
        }
        // Add other operators as needed
      });
    }

    // Add sorting
    if (sorters && sorters.length > 0) {
      sorters.forEach((sorter) => {
        constraints.push(
          orderBy(sorter.field, sorter.order === "asc" ? "asc" : "desc")
        );
      });
    }

    // Add pagination
    constraints.push(limit(pageSize));

    const queryRef = query(collection(db, resource), ...constraints);
    const snapshot = await getDocs(queryRef);

    const data = snapshot.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));

    return {
      data,
      total: data.length,
    };
  },

  getOne: async ({ resource, id }) => {
    const docRef = doc(db, resource, id as string);
    const docSnap = await getDoc(docRef);

    if (!docSnap.exists()) {
      throw new Error("Document not found");
    }

    return {
      data: {
        id: docSnap.id,
        ...docSnap.data(),
      },
    };
  },

  getMany: async ({ resource, ids }) => {
    const data = await Promise.all(
      ids.map(async (id) => {
        const docRef = doc(db, resource, id as string);
        const docSnap = await getDoc(docRef);
        return {
          id: docSnap.id,
          ...docSnap.data(),
        };
      })
    );

    return { data };
  },

  create: async ({ resource, variables }) => {
    const docRef = await addDoc(collection(db, resource), variables);

    const docSnap = await getDoc(docRef);

    return {
      data: {
        id: docRef.id,
        ...docSnap.data(),
      },
    };
  },

  update: async ({ resource, id, variables }) => {
    const docRef = doc(db, resource, id as string);
    await updateDoc(docRef, variables);

    const docSnap = await getDoc(docRef);

    return {
      data: {
        id: docSnap.id,
        ...docSnap.data(),
      },
    };
  },

  deleteOne: async ({ resource, id }) => {
    const docRef = doc(db, resource, id as string);
    await deleteDoc(docRef);

    return {
      data: { id },
    };
  },

  getApiUrl: () => "",
};

Basic Usage

Configure Refine

import { Refine } from "@refinedev/core";
import { dataProvider } from "./dataProvider";

const App = () => {
  return (
    <Refine
      dataProvider={dataProvider}
      resources={[
        {
          name: "posts",
          list: "/posts",
          create: "/posts/create",
          edit: "/posts/edit/:id",
          show: "/posts/show/:id",
        },
      ]}
    >
      {/* Your app content */}
    </Refine>
  );
};

Get List

import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  pagination: {
    current: 1,
    pageSize: 10,
  },
  sorters: [
    {
      field: "createdAt",
      order: "desc",
    },
  ],
});

Create Document

import { useCreate } from "@refinedev/core";

const { mutate } = useCreate();

const handleSubmit = (values) => {
  mutate({
    resource: "posts",
    values: {
      title: values.title,
      content: values.content,
      createdAt: new Date(),
    },
  });
};

Authentication

Implement authentication using Firebase Auth:
import { AuthProvider } from "@refinedev/core";
import {
  signInWithEmailAndPassword,
  signOut,
  onAuthStateChanged,
} from "firebase/auth";
import { auth } from "./firebase";

export const authProvider: AuthProvider = {
  login: async ({ email, password }) => {
    try {
      await signInWithEmailAndPassword(auth, email, password);
      return {
        success: true,
        redirectTo: "/",
      };
    } catch (error) {
      return {
        success: false,
        error,
      };
    }
  },
  logout: async () => {
    try {
      await signOut(auth);
      return {
        success: true,
        redirectTo: "/login",
      };
    } catch (error) {
      return {
        success: false,
        error,
      };
    }
  },
  check: async () => {
    return new Promise((resolve) => {
      onAuthStateChanged(auth, (user) => {
        if (user) {
          resolve({ authenticated: true });
        } else {
          resolve({ authenticated: false, redirectTo: "/login" });
        }
      });
    });
  },
  getIdentity: async () => {
    const user = auth.currentUser;
    if (user) {
      return {
        id: user.uid,
        name: user.displayName,
        email: user.email,
      };
    }
    return null;
  },
  onError: async (error) => {
    console.error(error);
    return { error };
  },
};

Real-time Updates

Implement real-time subscriptions using Firebase’s onSnapshot:
import { useEffect, useState } from "react";
import { collection, onSnapshot, query } from "firebase/firestore";
import { db } from "./firebase";

const PostList = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    const q = query(collection(db, "posts"));
    
    const unsubscribe = onSnapshot(q, (snapshot) => {
      const postsData = snapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));
      setPosts(postsData);
    });

    return () => unsubscribe();
  }, []);

  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </div>
      ))}
    </div>
  );
};

Storage Integration

Work with Firebase Storage for file uploads:
import { useState } from "react";
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";

const storage = getStorage();

const FileUpload = () => {
  const [uploading, setUploading] = useState(false);

  const handleUpload = async (file: File) => {
    try {
      setUploading(true);

      const storageRef = ref(storage, `uploads/${file.name}`);
      await uploadBytes(storageRef, file);

      const downloadURL = await getDownloadURL(storageRef);
      return downloadURL;
    } catch (error) {
      console.error("Error uploading file:", error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <input
      type="file"
      onChange={(e) => {
        const file = e.target.files?.[0];
        if (file) {
          handleUpload(file);
        }
      }}
      disabled={uploading}
    />
  );
};

Filtering

Implement filtering with Firestore queries:
import { useList } from "@refinedev/core";

const { data } = useList({
  resource: "posts",
  filters: [
    {
      field: "status",
      operator: "eq",
      value: "published",
    },
  ],
});

Complete Example

import { Refine } from "@refinedev/core";
import { dataProvider } from "./dataProvider";
import { authProvider } from "./authProvider";
import routerProvider from "@refinedev/react-router";
import { BrowserRouter } from "react-router";

const App = () => {
  return (
    <BrowserRouter>
      <Refine
        dataProvider={dataProvider}
        authProvider={authProvider}
        routerProvider={routerProvider}
        resources={[
          {
            name: "posts",
            list: "/posts",
            create: "/posts/create",
            edit: "/posts/edit/:id",
            show: "/posts/show/:id",
          },
        ]}
      >
        {/* Your routes and pages */}
      </Refine>
    </BrowserRouter>
  );
};

export default App;

Security Rules

Configure Firestore security rules:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow users to read and write their own posts
    match /posts/{postId} {
      allow read: if true;
      allow create: if request.auth != null;
      allow update, delete: if request.auth.uid == resource.data.userId;
    }
  }
}

Environment Variables

# .env
REACT_APP_FIREBASE_API_KEY=your_api_key
REACT_APP_FIREBASE_AUTH_DOMAIN=your_auth_domain
REACT_APP_FIREBASE_PROJECT_ID=your_project_id
REACT_APP_FIREBASE_STORAGE_BUCKET=your_storage_bucket
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
REACT_APP_FIREBASE_APP_ID=your_app_id

Best Practices

  1. Use environment variables for Firebase configuration
  2. Implement proper security rules
  3. Use Firestore indexes for complex queries
  4. Handle pagination properly with startAfter
  5. Implement proper error handling
  6. Use real-time listeners sparingly to avoid excessive reads

Next Steps

Data Providers Overview

Learn about other data providers

Firebase Documentation

Explore Firebase features

Build docs developers (and LLMs) love