Backend filters vs Frontend filters
Shubham Tiwari
Posted on September 9, 2024
Introduction
When building applications, filtering data is a common feature, especially in dynamic and data-driven applications. Filters can be implemented on both the backend and frontend of an application. While adding filters to the frontend might seem simpler, filtering data at the backend offers significant performance and cost advantages, especially when dealing with large datasets.
In this document, we'll cover why it's more efficient to filter data on the backend rather than on the frontend.
Backend vs Frontend Filtering: Key Differences
1. Frontend Filtering
Frontend filtering involves retrieving all the available data from the backend, then using JavaScript or other frontend technologies to apply the necessary filters. This approach can work for small datasets but comes with several drawbacks:
Pros:
Simple to implement for small datasets.
No need for backend changes if filters change frequently.
Cons:
Inefficient with large datasets: Fetching all the data from the backend can lead to excessive network usage, especially with large datasets.
Performance issues: Filtering large datasets in the frontend can lead to slow response times, especially on devices with limited processing power.
Increased data transfer costs: Fetching all data leads to higher server costs, especially if you're charged for data transfer (e.g., cloud services).
Security risks: Exposing the entire dataset to the frontend can lead to unintended data exposure.
2. Backend Filtering
Backend filtering involves the server handling the filtering of data and returning only the relevant results to the frontend. This is the preferred method for most scenarios, especially when dealing with large datasets.
Pros:
Efficient data transfer: Only the filtered, relevant data is sent to the frontend, reducing bandwidth usage.
Better performance: The backend, typically more powerful, handles the computation, leading to faster response times on the frontend.
Reduced client-side load: The frontend doesn't need to process large datasets, leading to a smoother user experience.
Security benefits: Sensitive data is kept on the server, and only the necessary information is shared with the client.
Cons:
- Requires backend modifications when adding or updating filters.
Why Backend Filtering is More Efficient
1. Reduced Bandwidth Usage
When you filter data on the frontend, you must fetch the entire dataset. This can lead to significant bandwidth usage, especially when the dataset is large. Backend filtering reduces this by only transferring the necessary, filtered data.
2. Lower Server and Database Load
Backend filtering allows you to optimize your database queries (e.g., using SQL WHERE clauses, filtering in MongoDB, or leveraging search engines like OpenSearch). This ensures that your database or server only retrieves the required subset of data, reducing load and improving performance.
3. Cost Efficiency
Many cloud services charge based on the amount of data transferred and processed. By filtering data at the backend, you minimize the amount of data sent to the client, which can lead to lower costs, particularly in data-heavy applications.
4. Improved User Experience
Backend filtering leads to faster load times since the frontend doesn't need to fetch and process a large dataset. The user experience improves as pages load faster, and users can get the information they need more quickly.
Example of Firebase and Next JS
- Firebase free quota and billing cycle.
Document reads and api calls are two different things, if a single api call fetches 100 documents, firebase will read it as 100 document reads not 1.
To make sure, we have better performance and cost effective api, we could add filters wherever needed like fetching User specific blogs from the api using where selector method. It is already there in many backend languages like SQL, MongoDB, Firebase firestore, etc.
Here’s the implementation for firebase
// Firebase fetching data using "where" method for users specific blogs
/**
* Fetches all blog documents from Firestore, with optional filter.
*
* @param filter - Optional filter to apply to the query.
* @returns An array of blog documents with their IDs.
*/
const db = getFirestore(app);
const blogsRef = collection(db, "blogs");
export const getBlogsFromDb = async (
filter: QueryFieldFilterConstraint | null,
) => {
try {
let q;
if (filter) {
q = query(blogsRef, filter);
} else {
q = query(blogsRef);
}
const querySnapshot = await getDocs(q);
const results = querySnapshot.docs.map((doc) => ({
id: doc.id,
...(doc.data() as BlogSchema),
}));
return results;
} catch (e) {
console.error("Error getting documents: ", e);
return [];
}
};
/**
* Fetches all blog documents from Firestore that belong to the given user ID.
*
* @param userId - The ID of the user whose blogs to fetch.
* @returns An array of blog documents with their IDs.
*/
export const getUserBlogsFromDb = async (userId: string) => {
const results = getBlogsFromDb(where("userId", "==", userId));
return results;
};
getBlogsFromDb method could be used to fetch all the blogs with some techiques like infiinite scrolling or load more button to fetch the incoming data on demand.
getUserBlogsFromDb could be used to fetch the users specific blogs using firebase where method, it will return those blogs where the userId of the blogs matches the client userId.
With this approach of filtering the data on the backend, we could save billing cost and have better page performance.
// Data fetching on the client side using NEXT JS.
"use client";
import React from "react";
import { getBlogsFromDb, getUserBlogsFromDb } from "../../server/dbMethods";
import BlogCard from "./Blogs/BlogCard";
import { useQuery } from "@tanstack/react-query";
import BlogCardSkeleton from "./Blogs/BlogCardSkeleton";
const BlogsFetch = ({
userId,
className,
}: {
userId?: string | null;
className?: string;
}) => {
const query = useQuery({
queryKey: userId ? ["blogs", userId] : ["blogs"],
queryFn: () =>
userId ? getUserBlogsFromDb(userId) : getBlogsFromDb([] as any),
});
return (
<>
<ul
className={`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10 justify-center lg:justify-start ${className}`}
>
{query.isPending ? (
[...Array(4)].map((_, i) => <BlogCardSkeleton key={i} />)
) : (
query?.data?.map((blog) => {
return <BlogCard userId={userId} key={blog.id} blog={blog} />;
})
)}
</ul>
</>
);
};
export default BlogsFetch;
Here we are conditionally calling the api methods based on the userId availability.
We are using tan stack query for data fetching as it is a great library to handle the api data, mutations, cache data and asynchronous state management along with many states like pending, error, success, etc.
Billing Reference - https://firebase.google.com/docs/firestore/pricing
That's it for this post
You can contact me on -
Instagram - https://www.instagram.com/supremacism__shubh/
LinkedIn - https://www.linkedin.com/in/shubham-tiwari-b7544b193/
Email - shubhmtiwri00@gmail.com
You can help me with some donation at the link below Thank you👇👇
https://www.buymeacoffee.com/waaduheck
Also check these posts as well
Posted on September 9, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024