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
Create Firebase project
Create a Firebase project in the Firebase Console.
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);
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’sonSnapshot:
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
- Use environment variables for Firebase configuration
- Implement proper security rules
- Use Firestore indexes for complex queries
- Handle pagination properly with
startAfter - Implement proper error handling
- Use real-time listeners sparingly to avoid excessive reads
Next Steps
Data Providers Overview
Learn about other data providers
Firebase Documentation
Explore Firebase features