Project Templates
Social Network
How To Build a Social Network with React
121 min
introduction in this tutorial, you will build back4gram, a full featured social network platform similar to instagram back4gram allows users to create accounts, share posts with images, interact through likes and comments, search for content, and communicate via real time messaging this project demonstrates how to combine react's powerful frontend capabilities with back4app's robust backend services to create a modern, feature rich social application react is the perfect choice for the frontend of a social network due to its component based architecture, which allows for reusable ui elements and efficient rendering meanwhile, back4app provides a managed parse server backend that handles user authentication, data storage, file uploads, and real time features without requiring you to build complex server infrastructure from scratch by completing this tutorial, you will build a complete social network with user authentication (signup, login, password reset) profile management post creation with image uploads social interactions (likes, comments) real time messaging with typing indicators content search functionality user settings and preferences along the way, you'll gain valuable skills in react development with hooks and context ui development with chakra ui parse server integration through back4app real time data management with livequery user authentication flows file upload handling responsive design implementation whether you're looking to launch your own social platform or simply want to understand how modern social networks are built, this tutorial will provide you with the knowledge and practical experience to achieve your goals at anypoint you can access the complete code on github prerequisites to complete this tutorial, you will need a back4app account sign up for a free account at back4app com https //www back4app com/ you'll use this to create and manage your backend services node js and npm installed on your local machine install node js (version 14 x or later) and npm from nodejs org https //nodejs org/ verify your installation by running node v and npm v in your terminal basic understanding of react familiarity with react components, hooks, and jsx if you need to brush up on react, check out the official react documentation https //reactjs org/docs/getting started html code editor any modern code editor like visual studio code, sublime text, or atom git (optional) for version control and following along with the repository supplementary resources back4app documentation https //www back4app com/docs/get started/welcome parse javascript guide https //docs parseplatform org/js/guide/ chakra ui documentation https //chakra ui com/docs/getting started react router documentation https //reactrouter com/en/main step 1 — setting up your back4app backend in this step, you'll create a new back4app project and configure the database schema needed for your social network application back4app provides a managed parse server that will handle user authentication, data storage, and real time features creating a new back4app project log in to your back4app account and navigate to the dashboard click on the "create a new app" button enter "back4gram" as your app name, select the closest server region, and click "create" once your app is created, you'll be redirected to the app dashboard understanding the database schema before creating classes in your database, let's understand the data model needed for our social network based on our application requirements, we'll need the following classes user (already exists by default in parse) this class handles user authentication and profile information we'll extend it with additional fields like bio and avatar post stores user posts including text content and images fields content (string), author (pointer to user), image (file), likes (number), comments (array), createdat (date) comment stores comments on posts fields content (string), author (pointer to user), post (pointer to post), createdat (date) conversation represents a chat conversation between users fields participants (array of pointers to user), lastmessage (string), updatedat (date) message individual messages within a conversation fields text (string), sender (pointer to user), conversation (pointer to conversation), createdat (date) typingstatus (for real time typing indicators) tracks when users are typing in a conversation fields user (pointer to user), conversation (pointer to conversation), istyping (boolean) creating database classes now, let's create these classes in your back4app database navigate to the "database" section in your back4app dashboard extending the user class click on the "user" class that already exists add the following columns bio (type string) avatar (type file) followers (type number, default 0) following (type number, default 0) creating the post class click "create a class" enter "post" as the class name and select "create an empty class" add the following columns content (type string) author (type pointer to user) image (type file) likes (type number, default 0) comments (type array) createdat (type date, automatically added) creating the comment class click "create a class" enter "comment" as the class name and select "create an empty class" add the following columns content (type string) author (type pointer to user) post (type pointer to post) createdat (type date, automatically added) creating the conversation class click "create a class" enter "conversation" as the class name and select "create an empty class" add the following columns participants (type array) lastmessage (type string) updatedat (type date, automatically added) creating the message class click "create a class" enter "message" as the class name and select "create an empty class" add the following columns text (type string) sender (type pointer to user) conversation (type pointer to conversation) createdat (type date, automatically added) creating the typingstatus class click "create a class" enter "typingstatus" as the class name and select "create an empty class" add the following columns user (type pointer to user) conversation (type pointer to conversation) istyping (type boolean) setting up class permissions (optional) to ensure data security, we need to configure appropriate access control lists (acls) for each class navigate to the "security & keys" section in your back4app dashboard under "class level security", configure the following permissions user class public read access enabled (so users can see other users' profiles) public write access disabled (users can only modify their own profiles) post class public read access enabled (everyone can see posts) public write access enabled (authenticated users can create posts) add a clp for update/delete to restrict to the author only comment class public read access enabled (everyone can see comments) public write access enabled (authenticated users can create comments) add a clp for update/delete to restrict to the author only conversation class public read access disabled (conversations are private) public write access enabled (authenticated users can create conversations) add a clp to restrict read/write access to conversation participants message class public read access disabled (messages are private) public write access enabled (authenticated users can send messages) add a clp to restrict read/write access to conversation participants typingstatus class public read access disabled (typing status is private) public write access enabled (authenticated users can update typing status) add a clp to restrict read/write access to conversation participants setting up livequery for real time features to enable real time features like messaging and typing indicators, we need to configure livequery navigate to the "server settings" section in your back4app dashboard under "parse server", find the "livequery" section and enable it add the following classes to be monitored by livequery message typingstatus post (for real time updates to likes and comments) save your changes getting your application keys you'll need your back4app application keys to connect your react frontend to the backend navigate to the "app settings" > "security & keys" section note down the following keys application id javascript key server url livequery server url (subdomain config, for real time features) you'll use these keys in your react application to initialize parse step 2 — creating a react frontend project in this step, you'll set up a new react project and configure it to work with your back4app backend you'll install the necessary dependencies, create the project structure, and connect to your parse server setting up a new react project let's start by creating a new react application using create react app, which provides a modern build setup with no configuration open your terminal and navigate to the directory where you want to create your project run the following command to create a new react application npx create react app back4gram once the project is created, navigate to the project directory cd back4gram start the development server to make sure everything is working npm start this will open your new react application in your browser at http //localhost 3000 http //localhost 3000 installing necessary dependencies now, let's install the packages we'll need for our social network application stop the development server (press ctrl+c in your terminal) install the parse sdk for connecting to back4app npm install parse install react router for navigation npm install react router dom install chakra ui for our user interface components npm install @chakra ui/react @emotion/react @emotion/styled framer motion install additional ui utilities and icon libraries npm install react icons project structure explanation let's organize our project with a clear structure create the following directories in the src folder mkdir p src/components/ui src/pages src/contexts src/utils here's what each directory is for components reusable ui components ui basic ui components like buttons, forms, modals other component folders for specific features (e g , posts, comments) pages full page components that correspond to routes contexts react context providers for state management utils utility functions and helpers creating environment variables to securely store your back4app credentials, create a env file in the root of your project create a new file named env local in the project root touch env local open the file and add your back4app credentials react app parse app id=your application id react app parse js key=your javascript key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your app id back4app io replace the placeholder values with your actual back4app credentials from step 1 make sure to add env local to your gitignore file to prevent committing sensitive information configuring parse sdk with back4app credentials now, let's set up the parse sdk to connect to your back4app backend create a new file src/utils/parseconfig js // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // initialize parse parse initialize( process env react app parse app id, process env react app parse js key ); parse serverurl = process env react app parse server url; // initialize live queries if (process env react app parse live query url) { parse livequeryserverurl = process env react app parse live query url; } export default parse; update your src/index js file to import the parse configuration import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import reportwebvitals from ' /reportwebvitals'; import ' /utils/parseconfig'; // import parse configuration const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); reportwebvitals(); setting up the app component with routing let's update the main app component to include routing and the chakra ui provider update src/app js import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider, extendtheme } from '@chakra ui/react'; // import pages (we'll create these next) import landingpage from ' /pages/landingpage'; import loginpage from ' /pages/loginpage'; import signuppage from ' /pages/signuppage'; import resetpasswordpage from ' /pages/resetpasswordpage'; import feedpage from ' /pages/feedpage'; import profilepage from ' /pages/profilepage'; import postdetailspage from ' /pages/postdetailspage'; import messagespage from ' /pages/messagespage'; import searchpage from ' /pages/searchpage'; import settingspage from ' /pages/settingspage'; // create a custom theme const theme = extendtheme({ config { initialcolormode 'dark', usesystemcolormode false, }, colors { brand { 50 '#e5f4ff', 100 '#b8dcff', 200 '#8ac5ff', 300 '#5cadff', 400 '#2e96ff', 500 '#147dff', 600 '#0061cc', 700 '#004799', 800 '#002d66', 900 '#001433', }, }, }); function app() { return ( \<chakraprovider theme={theme}> \<router> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/signup" element={\<signuppage />} /> \<route path="/reset password" element={\<resetpasswordpage />} /> \<route path="/feed" element={\<feedpage />} /> \<route path="/profile" element={\<profilepage />} /> \<route path="/post/\ id" element={\<postdetailspage />} /> \<route path="/messages" element={\<messagespage />} /> \<route path="/search" element={\<searchpage />} /> \<route path="/settings" element={\<settingspage />} /> \</routes> \</router> \</chakraprovider> ); } export default app; creating a protected route component to secure routes that require authentication, let's create a protectedroute component first, create an authcontext to manage user authentication state // src/contexts/authcontext js import react, { createcontext, usestate, usecontext, useeffect } from 'react'; import parse from 'parse/dist/parse min js'; const authcontext = createcontext(); export function useauth() { return usecontext(authcontext); } export function authprovider({ children }) { const \[currentuser, setcurrentuser] = usestate(null); const \[isloading, setisloading] = usestate(true); useeffect(() => { // check if user is already logged in const checkuser = async () => { try { const user = await parse user current(); setcurrentuser(user); } catch (error) { console error('error checking current user ', error); } finally { setisloading(false); } }; checkuser(); }, \[]); // login function const login = async (username, password) => { try { const user = await parse user login(username, password); setcurrentuser(user); return user; } catch (error) { throw error; } }; // signup function const signup = async (username, email, password) => { try { const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); const result = await user signup(); setcurrentuser(result); return result; } catch (error) { throw error; } }; // logout function const logout = async () => { try { await parse user logout(); setcurrentuser(null); } catch (error) { throw error; } }; // reset password function const resetpassword = async (email) => { try { await parse user requestpasswordreset(email); } catch (error) { throw error; } }; const value = { currentuser, isloading, login, signup, logout, resetpassword, }; return \<authcontext provider value={value}>{children}\</authcontext provider>; } now, create the protectedroute component // src/components/protectedroute js import react from 'react'; import { navigate } from 'react router dom'; import { useauth } from ' /contexts/authcontext'; import { flex, spinner } from '@chakra ui/react'; function protectedroute({ children }) { const { currentuser, isloading } = useauth(); if (isloading) { return ( \<flex justify="center" align="center" height="100vh"> \<spinner size="xl" /> \</flex> ); } if (!currentuser) { return \<navigate to="/login" />; } return children; } export default protectedroute; update the app component to use the authprovider and protectedroute // src/app js (updated) import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider, extendtheme } from '@chakra ui/react'; import { authprovider } from ' /contexts/authcontext'; import protectedroute from ' /components/protectedroute'; // import pages import landingpage from ' /pages/landingpage'; import loginpage from ' /pages/loginpage'; import signuppage from ' /pages/signuppage'; import resetpasswordpage from ' /pages/resetpasswordpage'; import feedpage from ' /pages/feedpage'; import profilepage from ' /pages/profilepage'; import postdetailspage from ' /pages/postdetailspage'; import messagespage from ' /pages/messagespage'; import searchpage from ' /pages/searchpage'; import settingspage from ' /pages/settingspage'; // theme configuration (same as before) const theme = extendtheme({ // theme configuration }); function app() { return ( \<chakraprovider theme={theme}> \<authprovider> \<router> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/signup" element={\<signuppage />} /> \<route path="/reset password" element={\<resetpasswordpage />} /> \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> \<route path="/profile" element={ \<protectedroute> \<profilepage /> \</protectedroute> } /> \<route path="/post/\ id" element={ \<protectedroute> \<postdetailspage /> \</protectedroute> } /> \<route path="/messages" element={ \<protectedroute> \<messagespage /> \</protectedroute> } /> \<route path="/search" element={ \<protectedroute> \<searchpage /> \</protectedroute> } /> \<route path="/settings" element={ \<protectedroute> \<settingspage /> \</protectedroute> } /> \</routes> \</router> \</authprovider> \</chakraprovider> ); } export default app; creating a basic landing page let's create a simple landing page to test our setup // src/pages/landingpage js import react from 'react'; import { box, heading, text, button, vstack, flex } from '@chakra ui/react'; import { link as routerlink } from 'react router dom'; function landingpage() { return ( \<box bg="gray 900" minh="100vh" color="white"> \<flex direction="column" align="center" justify="center" textalign="center" py={20} \> \<heading size="2xl" mb={4}> back4gram \</heading> \<text fontsize="lg" maxw="600px" mb={8}> join a vibrant community where your voice matters share stories, ideas, and moments with friends and the world \</text> \<vstack spacing={4} maxw="md" mx="auto"> \<button as={routerlink} to="/signup" colorscheme="brand" size="lg" w="full" \> create account \</button> \<button as={routerlink} to="/login" variant="outline" size="lg" w="full" \> log in \</button> \</vstack> \</flex> \</box> ); } export default landingpage; testing your setup now that you have set up the basic structure of your react application and connected it to back4app, let's test it start the development server npm start open your browser and navigate to http //localhost 3000 http //localhost 3000 you should see the landing page with buttons to sign up or log in check your browser console to make sure there are no errors related to the parse initialization step 3 — implementing authentication features in this step, we'll implement user authentication features for our social network application using back4app's parse server we'll examine how the parse authentication system works and implement login, signup, and password reset functionality understanding parse user authentication system back4app's parse server provides a comprehensive user management system through the parse user class let's understand how parse authentication works in our application the parse user class the parse user class is a special subclass of parse object designed specifically for user management in our back4gram application, we use it to store user credentials (username, email, password) manage authentication state handle session tokens automatically looking at our implementation, we can see how we interact with the parse user class // from signuppage js const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); await user signup(); this code creates a new parse user object, sets the required fields, and calls the signup() method to register the user in back4app authentication flow in parse let's examine how authentication works in our application registration flow in our signuppage js, we collect username, email, and password we validate the input data (checking for empty fields, valid email format, password length) we create a new parse user object and set the credentials we call signup() which sends the data to back4app parse hashes the password before storing it on success, the user is automatically logged in with a session token login flow in our loginpage js, we collect username and password we call parse user login() with these credentials parse verifies the credentials against the stored data if valid, parse generates a session token the session token is automatically stored in browser storage session management parse automatically includes the session token in all api requests we use parse user current() to retrieve the currently logged in user sessions persist across page refreshes implementing user registration let's examine our signuppage component to understand how user registration is implemented form validation before sending data to back4app, we validate the user input // from signuppage js const validateform = () => { const newerrors = {}; if (!username trim()) { newerrors username = 'username is required'; } if (!email trim()) { newerrors email = 'email is required'; } else if (!/\s+@\s+\\ \s+/ test(email)) { newerrors email = 'email is invalid'; } if (!password) { newerrors password = 'password is required'; } else if (password length < 6) { newerrors password = 'password must be at least 6 characters'; } if (password !== confirmpassword) { newerrors confirmpassword = 'passwords do not match'; } seterrors(newerrors); return object keys(newerrors) length === 0; }; this validation ensures that username is not empty email is valid password is at least 6 characters password and confirmation match handling registration errors our signup handler includes error handling for parse specific errors // from signuppage js try { // create a new user const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); await user signup(); toaster create({ title 'success', description 'account created successfully!', type 'success', }); navigate('/feed'); } catch (error) { toaster create({ title 'signup failed', description error message, type 'error', }); // handle specific parse errors if (error code === 202) { seterrors({ errors, username 'username already taken'}); } else if (error code === 203) { seterrors({ errors, email 'email already in use'}); } } back4app returns specific error codes that we can use to provide helpful feedback to the user code 202 username already taken code 203 email already in use the complete code for user registration/signup can be found here implementing user login our loginpage component handles user authentication using parse user login() login form the login form collects username and password // from loginpage js \<form onsubmit={handlelogin}> \<vstack spacing={4}> \<field label="username"> \<input type="text" value={username} onchange={(e) => setusername(e target value)} placeholder="your username" required /> \</field> \<field label="password" errortext={error} \> \<input type="password" value={password} onchange={(e) => setpassword(e target value)} placeholder="your password" required /> \</field> \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> \<button colorscheme="blue" width="full" type="submit" loading={isloading} \> log in \</button> \</vstack> \</form> session verification as shown earlier, we check for an existing session when the login page loads // from loginpage js useeffect(() => { const checkcurrentuser = async () => { try { const user = await parse user current(); if (user) { setcurrentuser(user); navigate('/feed'); } } catch (error) { console error('error checking current user ', error); } }; checkcurrentuser(); }, \[navigate]); this is a key feature of parse it automatically manages the session token in browser storage, allowing us to easily check if a user is already logged in implementing password reset back4app provides a built in password reset flow in our application, we link to a password reset page from the login form // from loginpage js \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> the password reset process in back4app works as follows user requests a password reset with their email parse sends a special reset link to the user's email user clicks the link and sets a new password parse updates the password hash in the database to implement this in our application, we would use // example password reset implementation try { await parse user requestpasswordreset(email); // show success message } catch (error) { // handle error } securing routes for authenticated users to protect certain routes in our application, we use a protectedroute component that checks if a user is authenticated // from protectedroute js function protectedroute({ children }) { const { currentuser, isloading } = useauth(); if (isloading) { return ( \<flex justify="center" align="center" height="100vh"> \<spinner size="xl" /> \</flex> ); } if (!currentuser) { return \<navigate to="/login" />; } return children; } this component uses our authcontext to check if a user is logged in shows a loading spinner while checking redirects to the login page if no user is found renders the protected content if a user is authenticated we use this component in our routing setup // from app js \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> back4app authentication configuration back4app provides several configuration options for authentication in the dashboard email verification you can require email verification before users can log in configure this in "server settings" > "parse server" > "user authentication" password policy set minimum password length and complexity requirements configure this in "server settings" > "parse server" > "user authentication" session length control how long user sessions remain valid configure this in "server settings" > "parse server" > "session configuration" email templates customize verification and password reset emails configure this in "app settings" > "email templates" testing your authentication implementation to ensure your authentication system works correctly test user registration try registering with valid credentials try registering with an existing username (should show an error) check if the user appears in your back4app dashboard under the " user" class test user login try logging in with correct credentials (should redirect to feed) try logging in with incorrect credentials (should show an error) test session persistence log in and refresh the page (should remain logged in) close and reopen the browser (should remain logged in if session is valid) test protected routes try accessing /feed when logged out (should redirect to login) try accessing /feed when logged in (should show the feed page) the code for login component can be founde here step 4 — developing the feed functionality in this step, you'll implement the core social networking feature the feed this is where users will create posts, view content from others, and interact through likes and comments we'll use back4app's parse server to store and retrieve posts, handle file uploads for images, and implement real time updates understanding the feed page structure the feed page in our application has three main components a sidebar for navigation the main feed area with post creation and post listing a trending section (on larger screens) let's examine how this is implemented in our feedpage js // from feedpage js main structure function feedpage() { // state and hooks return ( \<flex minh="100vh" bg="gray 800" color="white"> {/ left sidebar (navigation) /} \<box w={\['0px', '200px']} bg="gray 900" p={4} display={\['none', 'block']}> {/ navigation links /} \</box> {/ main content (feed) /} \<box flex="1" p={4} overflowy="auto"> {/ post creation form /} {/ posts list /} \</box> {/ right sidebar (trending) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']}> {/ trending content /} \</box> \</flex> ); } this responsive layout adapts to different screen sizes, hiding the sidebars on mobile devices creating the post class in back4app before implementing the frontend, let's ensure our back4app database is properly set up for posts the post class should have the following fields content (string) the text content of the post image (file) optional image attachment author (pointer to user) the user who created the post likes (number) count of likes on the post likedby (array) array of user ids who liked the post createdat (date) automatically added by parse set appropriate permissions for the post class public read access everyone should be able to read posts public write access authenticated users should be able to create posts update/delete permissions only the author should be able to modify their posts implementing post creation let's examine how post creation is implemented in our feedpage component // from feedpage js post creation state const \[postcontent, setpostcontent] = usestate(''); const \[postimage, setpostimage] = usestate(null); const \[imagepreview, setimagepreview] = usestate(''); const \[issubmitting, setissubmitting] = usestate(false); // image selection handler const handleimageselect = (e) => { if (e target files && e target files\[0]) { const file = e target files\[0]; setpostimage(file); // create preview url const previewurl = url createobjecturl(file); setimagepreview(previewurl); } }; // post submission handler const handlesubmitpost = async () => { if (!postcontent trim() && !postimage) { toaster create({ title 'error', description 'please add some text or an image to your post', type 'error', }); return; } setissubmitting(true); try { // create a new post object const post = parse object extend('post'); const newpost = new post(); // set post content newpost set('content', postcontent); newpost set('author', parse user current()); newpost set('likes', 0); newpost set('likedby', \[]); // if there's an image, upload it if (postimage) { const parsefile = new parse file(postimage name, postimage); await parsefile save(); newpost set('image', parsefile); } // save the post await newpost save(); // reset form setpostcontent(''); setpostimage(null); setimagepreview(''); // refresh posts fetchposts(); toaster create({ title 'success', description 'your post has been published!', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } finally { setissubmitting(false); } }; key points about post creation file handling in parse parse file is used to upload images to back4app's storage the file is saved first, then attached to the post object back4app automatically handles file storage and generates urls creating parse objects we extend the 'post' class with parse object extend('post') we create a new instance with new post() we set properties using the set() method we save the object to back4app with save() user association we associate the post with the current user using parse user current() this creates a pointer relationship in the database the post creation form ui looks like this {/ post creation form /} \<box mb={6} bg="gray 700" p={4} borderradius="md"> \<vstack align="stretch" spacing={4}> \<textarea placeholder="what's on your mind?" value={postcontent} onchange={(e) => setpostcontent(e target value)} minh="100px" /> {imagepreview && ( \<box position="relative"> \<image src={imagepreview} maxh="200px" borderradius="md" /> \<iconbutton icon={\<closeicon />} size="sm" position="absolute" top="2" right="2" onclick={() => { setpostimage(null); setimagepreview(''); }} /> \</box> )} \<hstack> \<button lefticon={\<attachmenticon />} onclick={() => document getelementbyid('image upload') click()} \> add image \</button> \<input id="image upload" type="file" accept="image/ " onchange={handleimageselect} display="none" /> \<button colorscheme="blue" ml="auto" isloading={issubmitting} onclick={handlesubmitpost} disabled={(!postcontent trim() && !postimage) || issubmitting} \> post \</button> \</hstack> \</vstack> \</box> fetching and displaying posts now let's look at how we fetch and display posts from back4app // from feedpage js fetching posts const \[posts, setposts] = usestate(\[]); const \[isloading, setisloading] = usestate(true); const \[page, setpage] = usestate(0); const \[hasmore, sethasmore] = usestate(true); const postsperpage = 10; const fetchposts = async (loadmore = false) => { try { const currentpage = loadmore ? page + 1 0; // create a query for the post class const post = parse object extend('post'); const query = new parse query(post); // include the author object (pointer) query include('author'); // sort by creation date, newest first query descending('createdat'); // pagination query limit(postsperpage); query skip(currentpage postsperpage); // execute the query const results = await query find(); // process the results const fetchedposts = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); // update state if (loadmore) { setposts(prevposts => \[ prevposts, fetchedposts]); setpage(currentpage); } else { setposts(fetchedposts); setpage(0); } // check if there are more posts to load sethasmore(results length === postsperpage); } catch (error) { console error('error fetching posts ', error); toaster create({ title 'error', description 'failed to load posts please try again ', type 'error', }); } finally { setisloading(false); } }; // load posts when component mounts useeffect(() => { fetchposts(); }, \[]); key points about fetching posts parse queries we create a query with new parse query(post) we include related objects with query include('author') we sort with query descending('createdat') we paginate with query limit() and query skip() we execute the query with query find() processing results parse objects have a get() method to access properties for file fields, we use file url() to get the url we transform parse objects into plain javascript objects for react state pagination we implement "load more" functionality with page tracking we check if there are more posts to load before making additional requests the posts are displayed in a list {/ posts list /} {isloading ? ( \<center py={10}> \<spinner size="xl" /> \</center> ) posts length > 0 ? ( \<vstack spacing={4} align="stretch"> {posts map(post => ( \<box key={post id} p={4} bg="gray 700" borderradius="md"> {/ post header with author info /} \<hstack mb={2}> \<avatar root size="sm"> \<avatar fallback name={post author username} /> \<avatar image src={post author avatar} /> \</avatar root> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(post createdat)}\</text> \</hstack> {/ post content /} \<text mb={4}>{post content}\</text> {/ post image if any /} {post image && ( \<image src={post image} maxh="400px" borderradius="md" mb={4} /> )} {/ post actions /} \<hstack> \<button variant="ghost" lefticon={\<likeicon />} onclick={() => handlelikepost(post id, post likedby)} color={post likedby includes(currentuser id) ? "blue 400" "white"} \> {post likes} likes \</button> \<button variant="ghost" lefticon={\<commenticon />} as={routerlink} to={`/post/${post id}`} \> comments \</button> \</hstack> \</box> ))} {/ load more button /} {hasmore && ( \<button onclick={() => fetchposts(true)} isloading={isloadingmore}> load more \</button> )} \</vstack> ) ( \<center py={10}> \<text>no posts yet be the first to post!\</text> \</center> )} implementing like functionality let's examine how the like functionality is implemented // from feedpage js like functionality const handlelikepost = async (postid, likedby) => { try { const currentuserid = parse user current() id; const isliked = likedby includes(currentuserid); // get the post object const post = parse object extend('post'); const query = new parse query(post); const post = await query get(postid); // update likes count and likedby array if (isliked) { // unlike remove user from likedby and decrement likes post set('likedby', likedby filter(id => id !== currentuserid)); post set('likes', (post get('likes') || 1) 1); } else { // like add user to likedby and increment likes post set('likedby', \[ likedby, currentuserid]); post set('likes', (post get('likes') || 0) + 1); } // save the updated post await post save(); // update local state setposts(prevposts => prevposts map(p => p id === postid ? { p, likes isliked ? p likes 1 p likes + 1, likedby isliked ? p likedby filter(id => id !== currentuserid) \[ p likedby, currentuserid] } p ) ); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'failed to like post please try again ', type 'error', }); } }; key points about the like functionality optimistic updates we update the ui immediately before the server confirms the change this makes the app feel more responsive parse object updates we fetch the specific post with query get(postid) we modify its properties with post set() we save the changes with post save() tracking likes we maintain both a count ( likes ) and a list of users ( likedby ) this allows us to show accurate counts and determine if the current user has liked a post implementing real time updates with livequery (optional) to make the feed update in real time when new posts are created, we can use parse livequery // from feedpage js livequery setup const livequerysubscription = useref(null); useeffect(() => { // set up livequery for real time updates const setuplivequery = async () => { try { const post = parse object extend('post'); const query = new parse query(post); // subscribe to new posts livequerysubscription current = await query subscribe(); // when a new post is created livequerysubscription current on('create', (post) => { // only add to feed if it's not already there setposts(prevposts => { if (prevposts some(p => p id === post id)) return prevposts; const newpost = { id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } }; return \[newpost, prevposts]; }); }); // when a post is updated (e g , liked) livequerysubscription current on('update', (post) => { setposts(prevposts => prevposts map(p => p id === post id ? { p, content post get('content'), image post get('image') ? post get('image') url() p image, likes post get('likes') || 0, likedby post get('likedby') || \[] } p ) ); }); } catch (error) { console error('error setting up livequery ', error); } }; setuplivequery(); // clean up subscription when component unmounts return () => { if (livequerysubscription current) { livequerysubscription current unsubscribe(); } }; }, \[]); key points about livequery subscription setup we create a query and subscribe to it with query subscribe() this establishes a websocket connection to back4app's livequery server event handling we listen for 'create' events when new posts are created we listen for 'update' events when posts are modified we update our local state accordingly cleanup we unsubscribe when the component unmounts to prevent memory leaks optimizing post loading with pagination we've already implemented basic pagination with the "load more" button let's enhance it with infinite scrolling // from feedpage js infinite scrolling const \[isloadingmore, setisloadingmore] = usestate(false); const feedref = useref(null); // intersection observer for infinite scrolling useeffect(() => { if (!hasmore) return; const observer = new intersectionobserver( (entries) => { if (entries\[0] isintersecting && !isloading && !isloadingmore) { loadmoreposts(); } }, { threshold 0 5 } ); const loadmoretrigger = document getelementbyid('load more trigger'); if (loadmoretrigger) { observer observe(loadmoretrigger); } return () => { if (loadmoretrigger) { observer unobserve(loadmoretrigger); } }; }, \[hasmore, isloading, isloadingmore]); const loadmoreposts = async () => { if (!hasmore || isloadingmore) return; setisloadingmore(true); try { await fetchposts(true); } finally { setisloadingmore(false); } }; and add this at the end of the posts list {/ infinite scroll trigger /} {hasmore && ( \<box id="load more trigger" h="20px" /> )} key points about infinite scrolling intersection observer we use the intersection observer api to detect when the user scrolls to the bottom when the trigger element becomes visible, we load more posts loading states we track separate loading states for initial load and "load more" this prevents multiple simultaneous requests performance considerations we only load a fixed number of posts at a time (pagination) we check if there are more posts to load before making additional requests back4app performance optimization to optimize performance when working with back4app use indexes add indexes to frequently queried fields in your back4app dashboard for the post class, add indexes on 'createdat' and 'author' selective queries use query select() to only fetch the fields you need this reduces data transfer and improves performance count optimization instead of fetching all posts to count them, use query count() this is more efficient for determining total counts step 6 — adding social interactions in this step, we'll enhance our social network by implementing key social interaction features comments on posts, user profiles, and user settings we'll focus on how these features interact with the back4app backend and the mechanisms that make them work implementing comments on posts comments are a fundamental social interaction feature that requires proper data modeling in back4app let's examine how our application interacts with the parse server to implement comments back4app data model for comments in back4app, comments are implemented as a separate class with relationships to both users and posts comment class structure content (string) the text content of the comment author (pointer to user) points to the user who created the comment post (pointer to post) points to the post being commented on createdat (date) automatically managed by parse relationship types user → comments one to many (one user can create many comments) post → comments one to many (one post can have many comments) fetching comments from back4app our postdetailspage uses parse queries to fetch comments for a specific post // from postdetailspage js comment fetching const fetchcomments = async () => { try { // create a query on the comment class const comment = parse object extend('comment'); const query = new parse query(comment); // find comments for this specific post using a pointer equality constraint query equalto('post', postobject); // include the author information query include('author'); // sort by creation date (newest first) query descending('createdat'); // execute the query const results = await query find(); // transform parse objects to plain objects for react state const commentslist = results map(comment => ({ id comment id, content comment get('content'), createdat comment get('createdat'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null } })); setcomments(commentslist); } catch (error) { console error('error fetching comments ', error); toaster create({ title 'error', description 'failed to load comments', type 'error', }); } }; key back4app mechanisms parse object extend() creates a reference to the comment class in back4app query equalto() creates a constraint to find only comments for a specific post query include() performs a join like operation to fetch related objects in a single query query descending() sorts the results by a specific field creating comments in back4app when a user adds a comment, we create a new parse object and establish the relationships // from postdetailspage js adding a comment const handleaddcomment = async (e) => { e preventdefault(); if (!newcomment trim()) { return; } setiscommenting(true); try { // create a new comment object in back4app const comment = parse object extend('comment'); const comment = new comment(); // set comment data and relationships comment set('content', newcomment); comment set('author', parse user current()); // pointer to current user comment set('post', postobject); // pointer to current post // save the comment to back4app await comment save(); // clear the input setnewcomment(''); // refresh comments fetchcomments(); toaster create({ title 'success', description 'your comment has been added', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } finally { setiscommenting(false); } }; key back4app mechanisms new comment() creates a new instance of the comment class comment set() sets properties on the parse object, including pointers to related objects comment save() sends the object to back4app for storage parse user current() gets the currently authenticated user to establish the author relationship back4app security for comments to properly secure comments in back4app configure class level permissions (clps) read public (everyone can read comments) write authenticated users only (only logged in users can comment) update/delete creator only (only the comment author can modify or delete) set up these permissions in your back4app dashboard { "find" { " " true }, "get" { " " true }, "create" { " " true }, "update" { "requiresauthentication" true }, "delete" { "requiresauthentication" true }, "addfield" { "requiresauthentication" true } } step 7 building user profiles with back4app user profiles in our application leverage parse's built in user class with custom fields let's examine how profilepage js interacts with back4app back4app user class extensions the parse user class is extended with additional fields for our social network custom user fields avatar (file) profile picture stored in back4app's file storage bio (string) user biography website (string) user's website url displayname (string) user's display name fetching user data and posts our profilepage fetches both user data and the user's posts // from profilepage js profile data fetching const fetchuserdata = async () => { try { // get current user from parse session const currentuser = await parse user current(); if (!currentuser) { navigate('/login'); return; } setuser(currentuser); // create a query to find posts by this user const post = parse object extend('post'); const query = new parse query(post); query equalto('author', currentuser); query include('author'); query descending('createdat'); const results = await query find(); // transform parse objects to plain objects const postslist = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setposts(postslist); setstats(prevstats => ({ prevstats, posts postslist length })); } catch (error) { console error('error fetching user data ', error); toaster create({ title 'error', description 'failed to load profile data', type 'error', }); } finally { setisloading(false); } }; key back4app mechanisms parse user current() retrieves the authenticated user from the session token query equalto('author', currentuser) creates a pointer equality constraint to find posts by the current user post get('image') url() accesses the url of a parse file object stored in back4app implementing user settings the settingspage allows users to update their profile information and manage account settings let's examine how it interacts with back4app // from settingspage js user settings implementation function settingspage() { const \[privacysettings, setprivacysettings] = usestate({ profilevisibility 'public', postprivacy 'friends' }); const \[twofactorauth, settwofactorauth] = usestate(false); const \[isopen, setisopen] = usestate(false); const cancelref = useref(); // save user settings to back4app const savesettings = async (settingstype, settingsdata) => { try { const currentuser = await parse user current(); if (!currentuser) { toaster create({ title 'error', description 'you must be logged in to save settings', type 'error', }); return; } // update the appropriate settings based on type switch (settingstype) { case 'privacy' currentuser set('privacysettings', settingsdata); break; case 'security' currentuser set('securitysettings', settingsdata); break; case 'notifications' currentuser set('notificationsettings', settingsdata); break; default break; } // save the user object await currentuser save(); toaster create({ title 'success', description 'your settings have been saved', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } }; return ( \<box maxw="800px" mx="auto" p={4}> \<heading mb={6}>account settings\</heading> \<tabs root defaultvalue="profile"> \<tabs list> \<tabs trigger value="profile">profile\</tabs trigger> \<tabs trigger value="privacy">privacy\</tabs trigger> \<tabs trigger value="security">security\</tabs trigger> \<tabs trigger value="notifications">notifications\</tabs trigger> \<tabs indicator /> \</tabs list> {/ settings tabs content /} {/ /} \</tabs root> {/ account deactivation dialog /} \<dialog root open={isopen} onopenchange={setisopen}> {/ /} \</dialog root> \</box> ); } key back4app mechanisms parse user current() gets the current user to update their settings currentuser set() updates user properties in the parse user object currentuser save() persists the changes to back4app back4app user settings schema to implement settings in back4app add these fields to the user class privacysettings (object) json object containing privacy preferences securitysettings (object) json object containing security settings notificationsettings (object) json object containing notification preferences example schema for these objects // privacysettings { "profilevisibility" "public", // or "friends" or "private" "postprivacy" "friends", // or "public" or "private" "showactivity" true } // securitysettings { "twofactorauth" false, "loginalerts" true } // notificationsettings { "likes" true, "comments" true, "follows" true, "messages" true } back4app cloud functions for social interactions for more complex social interactions, you can implement cloud functions in back4app for example, to track comment notifications // example cloud function for comment notifications parse cloud aftersave("comment", async (request) => { // only run for new comments, not updates if (request original) return; const comment = request object; const post = comment get("post"); const commenter = request user; // skip if user is commenting on their own post const postquery = new parse query("post"); const fullpost = await postquery get(post id, { usemasterkey true }); const postauthor = fullpost get("author"); if (postauthor id === commenter id) return; // create a notification const notification = parse object extend("notification"); const notification = new notification(); notification set("type", "comment"); notification set("fromuser", commenter); notification set("touser", postauthor); notification set("post", post); notification set("read", false); await notification save(null, { usemasterkey true }); }); to implement this go to your back4app dashboard navigate to "cloud code" > "cloud functions" create a new function with the code above deploy the function step 8 — building real time messaging in this step, we'll implement real time messaging functionality using back4app's livequery feature this will allow users to exchange messages instantly without refreshing the page, creating a dynamic chat experience similar to popular messaging platforms understanding back4app livequery before diving into the implementation, let's understand how back4app's livequery works what is livequery? livequery is a feature of parse server that allows clients to subscribe to queries when objects matching these queries change, the server automatically pushes updates to subscribed clients this creates real time functionality without implementing complex websocket handling yourself how livequery works livequery establishes a websocket connection between the client and server clients subscribe to specific queries they want to monitor when data matching these queries changes, the server sends events over the websocket the client receives these events and updates the ui accordingly livequery events create triggered when a new object matching the query is created update triggered when an existing object matching the query is updated enter triggered when an object starts matching the query leave triggered when an object no longer matches the query delete triggered when an object matching the query is deleted setting up livequery in back4app to enable livequery for your application, follow these steps enable your back4app subdomain log in to your back4app account navigate to "app settings" > "server settings" find the "server url and live query" block and click on "settings" check the "activate your back4app subdomain" option this subdomain will serve as your livequery server activate livequery on the same settings page, check the "activate live query" option select the classes you want to monitor with livequery message (for chat messages) typingstatus (for typing indicators) conversation (for conversation updates) save your changes note your livequery server url your livequery server url will be in the format wss\ //yourappname back4app io you'll need this url to initialize the livequery client in your react application configuring livequery in your react app to use livequery in your react application, you need to initialize a livequery client // from parseconfig js or app js parse initialize( process env react app parse app id, process env react app parse js key ); parse serverurl = process env react app parse server url; // initialize live queries with your subdomain parse livequeryserverurl = process env react app parse live query url; // e g , 'wss\ //yourappname back4app io' in your env local file, make sure to include react app parse live query url=wss\ //yourappname back4app io creating the data model for messaging our messaging system requires two main classes in back4app conversation class participants (array) array of user pointers for the users in the conversation lastmessage (string) the content of the most recent message lastmessagedate (date) timestamp of the most recent message updatedat (date) automatically managed by parse message class conversation (pointer) points to the conversation this message belongs to sender (pointer) points to the user who sent the message content (string) the text content of the message read (boolean) whether the message has been read createdat (date) automatically managed by parse typingstatus class conversation (pointer) points to the conversation user (pointer) points to the user who is typing istyping (boolean) whether the user is currently typing implementing the messaging interface let's examine how our messagespage implements real time messaging // from messagespage js component structure function messagespage() { const \[conversations, setconversations] = usestate(\[]); const \[selectedconversation, setselectedconversation] = usestate(null); const \[messages, setmessages] = usestate(\[]); const \[newmessage, setnewmessage] = usestate(''); const \[isloading, setisloading] = usestate(true); const \[typingusers, settypingusers] = usestate(\[]); const messagesendref = useref(null); const messagesubscription = useref(null); const typingsubscription = useref(null); const conversationsubscription = useref(null); const typingtimeoutref = useref(null); // rest of the component } the component maintains several pieces of state conversations list of the user's conversations selectedconversation the currently selected conversation messages messages in the selected conversation typingusers users currently typing in the conversation it also uses refs to store livequery subscriptions and manage typing indicators subscribing to livequery for messages the key to real time messaging is subscribing to livequery for messages in the current conversation // from messagespage js livequery subscription for messages const subscribetomessages = async (conversation) => { try { // unsubscribe from previous subscription if exists if (messagesubscription current) { messagesubscription current unsubscribe(); } // create a query for messages in this conversation const message = parse object extend('message'); const query = new parse query(message); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversation id; // find messages for this conversation query equalto('conversation', conversationpointer); // include the sender information query include('sender'); // subscribe to the query const subscription = await query subscribe(); messagesubscription current = subscription; // when a new message is created subscription on('create', (message) => { // add the new message to our state setmessages(prevmessages => { // check if message already exists in our list const exists = prevmessages some(m => m id === message id); if (exists) return prevmessages; // add the new message return \[ prevmessages, { id message id, content message get('content'), createdat message get('createdat'), sender { id message get('sender') id, username message get('sender') get('username'), avatar message get('sender') get('avatar') ? message get('sender') get('avatar') url() null }, read message get('read') }]; }); // scroll to bottom when new message arrives scrolltobottom(); // mark message as read if from other user if (message get('sender') id !== parse user current() id) { markmessageasread(message); } }); } catch (error) { console error('error subscribing to messages ', error); } }; key livequery mechanisms creating a query we create a query for messages in the current conversation subscribing to the query we call query subscribe() to start listening for changes handling events we use subscription on('create', callback) to handle new messages unsubscribing we store the subscription reference and unsubscribe when needed implementing typing indicators with livequery typing indicators are another real time feature implemented with livequery // from messagespage js livequery for typing indicators const subscribetotypingstatus = async (conversation) => { try { // unsubscribe from previous subscription if exists if (typingsubscription current) { typingsubscription current unsubscribe(); } // create a query for typing status in this conversation const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversation id; // find typing status for this conversation query equalto('conversation', conversationpointer); // include the user information query include('user'); // subscribe to the query const subscription = await query subscribe(); typingsubscription current = subscription; // when a typing status is updated subscription on('update', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // update typing users list settypingusers(prevtypingusers => { // if user is typing, add them to the list if (istyping) { // check if user is already in the list const exists = prevtypingusers some(u => u id === user id); if (exists) return prevtypingusers; // add user to typing list return \[ prevtypingusers, { id user id, username user get('username') }]; } else { // if user stopped typing, remove them from the list return prevtypingusers filter(u => u id !== user id); } }); }); // when a new typing status is created subscription on('create', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // only add to typing users if they are actually typing if (istyping && user id !== parse user current() id) { settypingusers(prevtypingusers => { // check if user is already in the list const exists = prevtypingusers some(u => u id === user id); if (exists) return prevtypingusers; // add user to typing list return \[ prevtypingusers, { id user id, username user get('username') }]; }); } }); } catch (error) { console error('error subscribing to typing status ', error); } }; updating typing status when a user types, we update their typing status // from messagespage js updating typing status const updatetypingstatus = async (istyping) => { if (!selectedconversation) return; try { const currentuser = await parse user current(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = selectedconversation id; // check if a typing status already exists const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); query equalto('conversation', conversationpointer); query equalto('user', currentuser); let typingstatus = await query first(); if (typingstatus) { // update existing typing status typingstatus set('istyping', istyping); } else { // create new typing status typingstatus = new typingstatus(); typingstatus set('conversation', conversationpointer); typingstatus set('user', currentuser); typingstatus set('istyping', istyping); } await typingstatus save(); } catch (error) { console error('error updating typing status ', error); } }; // handle typing indicator with debounce const handletyping = () => { updatetypingstatus(true); // clear previous timeout if (typingtimeoutref current) { cleartimeout(typingtimeoutref current); } // set typing to false after 3 seconds of inactivity typingtimeoutref current = settimeout(() => { updatetypingstatus(false); }, 3000); }; sending messages when sending messages, we create a new message object and let livequery handle the updates // from messagespage js sending messages const sendmessage = async (e) => { e preventdefault(); if (!newmessage trim() || !selectedconversation) return; try { const currentuser = await parse user current(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = selectedconversation id; // create a new message const message = parse object extend('message'); const message = new message(); message set('content', newmessage); message set('sender', currentuser); message set('conversation', conversationpointer); message set('read', false); await message save(); // update the conversation with the last message const conversation = await new parse query(conversation) get(selectedconversation id); conversation set('lastmessage', newmessage); conversation set('lastmessagedate', new date()); await conversation save(); // clear the input setnewmessage(''); // set typing status to false updatetypingstatus(false); } catch (error) { console error('error sending message ', error); toast({ title 'error', description 'failed to send message', status 'error', duration 3000, isclosable true, }); } }; cleaning up livequery subscriptions it's important to clean up livequery subscriptions when they're no longer needed // from messagespage js cleanup useeffect(() => { // fetch initial conversations fetchconversations(); subscribetoconversations(); // cleanup subscriptions when component unmounts return () => { if (messagesubscription current) { messagesubscription current unsubscribe(); } if (typingsubscription current) { typingsubscription current unsubscribe(); } if (conversationsubscription current) { conversationsubscription current unsubscribe(); } if (typingtimeoutref current) { cleartimeout(typingtimeoutref current); } }; }, \[]); back4app livequery performance considerations when implementing livequery, consider these performance tips be specific with queries subscribe only to the data you need use constraints to limit the scope of subscriptions for example, only subscribe to messages in the current conversation manage subscriptions carefully unsubscribe when data is no longer needed create new subscriptions when the context changes store subscription references to unsubscribe later use acls for security set proper acls on message and conversation objects ensure users can only access conversations they're part of livequery respects acls, so unauthorized users won't receive updates optimize livequery server in back4app dashboard, configure the classes that need livequery don't enable livequery for classes that don't need real time updates step 9 — implementing search functionality in this step, we'll implement comprehensive search functionality for our social network users will be able to search for other users, posts by content, and hashtags this feature will make it easier for users to discover content and connect with others on the platform understanding search in back4app before diving into implementation, let's understand how search works in back4app parse query system back4app uses parse server's query system for searching queries can be performed across multiple classes you can search by exact match, contains, starts with, etc text search options startswith finds strings that begin with a specific string contains finds strings that contain a specific substring matches uses regular expressions for more complex pattern matching fulltext (enterprise feature) provides advanced full text search capabilities performance considerations text searches can be resource intensive indexes should be created for frequently searched fields queries should be optimized to limit the number of results building the search page our searchpage component will handle different types of searches and display the results let's examine its structure // from searchpage js component structure function searchpage() { const \[searchquery, setsearchquery] = usestate(''); const \[searchtype, setsearchtype] = usestate('users'); // 'users', 'posts', 'hashtags' const \[searchresults, setsearchresults] = usestate(\[]); const \[isloading, setisloading] = usestate(false); const \[trendingtopics, settrendingtopics] = usestate(\[]); // rest of the component } the component maintains state for the search query entered by the user the type of search being performed the search results loading state trending topics implementing user search let's look at how we search for users in back4app // from searchpage js user search implementation const searchusers = async (query) => { setisloading(true); try { // create a query on the user class const userquery = new parse query(parse user); // search for usernames that contain the query string (case insensitive) userquery matches('username', new regexp(query, 'i')); // limit results to improve performance userquery limit(20); const users = await userquery find(); // transform parse objects to plain objects const userresults = users map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null, bio user get('bio') || '' })); setsearchresults(userresults); } catch (error) { console error('error searching users ', error); toaster create({ title 'error', description 'failed to search users', type 'error', }); } finally { setisloading(false); } }; key back4app mechanisms new parse query(parse user) creates a query on the user class userquery matches('username', new regexp(query, 'i')) performs a case insensitive regex match on usernames userquery limit(20) limits results to improve performance userquery find() executes the query and returns matching users implementing post content search now let's look at how we search for posts by content // from searchpage js post search implementation const searchposts = async (query) => { setisloading(true); try { // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the query string postquery matches('content', new regexp(query, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(20); const posts = await postquery find(); // transform parse objects to plain objects const postresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setsearchresults(postresults); } catch (error) { console error('error searching posts ', error); toaster create({ title 'error', description 'failed to search posts', type 'error', }); } finally { setisloading(false); } }; key back4app mechanisms parse object extend('post') references the post class postquery matches('content', new regexp(query, 'i')) performs a case insensitive regex match on post content postquery include('author') includes the author information in a single query postquery descending('createdat') sorts results by creation date implementing hashtag search hashtag search requires a different approach we'll search for posts that contain hashtags // from searchpage js hashtag search implementation const searchhashtags = async (query) => { setisloading(true); try { // remove # if present at the beginning const hashtagquery = query startswith('#') ? query substring(1) query; // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the hashtag // we use word boundaries to find actual hashtags postquery matches('content', new regexp(`#${hashtagquery}\\\b`, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(20); const posts = await postquery find(); // transform parse objects to plain objects const hashtagresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setsearchresults(hashtagresults); } catch (error) { console error('error searching hashtags ', error); toaster create({ title 'error', description 'failed to search hashtags', type 'error', }); } finally { setisloading(false); } }; key back4app mechanisms we use a regex with word boundaries ( \\\b ) to find actual hashtags this approach finds posts where the content contains the specific hashtag implementing trending topics to implement trending topics, we need to analyze recent posts and count hashtag occurrences // from searchpage js fetching trending topics const fetchtrendingtopics = async () => { try { // create a query on the post class const post = parse object extend('post'); const query = new parse query(post); // get posts from the last 7 days const oneweekago = new date(); oneweekago setdate(oneweekago getdate() 7); query greaterthan('createdat', oneweekago); // limit to a reasonable number for analysis query limit(500); const posts = await query find(); // extract hashtags from post content const hashtagcounts = {}; posts foreach(post => { const content = post get('content') || ''; // find all hashtags in the content const hashtags = content match(/#(\w+)/g) || \[]; // count occurrences of each hashtag hashtags foreach(hashtag => { const tag = hashtag tolowercase(); hashtagcounts\[tag] = (hashtagcounts\[tag] || 0) + 1; }); }); // convert to array and sort by count const trendingarray = object entries(hashtagcounts) map((\[hashtag, count]) => ({ hashtag, count })) sort((a, b) => b count a count) slice(0, 10); // get top 10 settrendingtopics(trendingarray); } catch (error) { console error('error fetching trending topics ', error); } }; key back4app mechanisms we query posts from the last 7 days using query greaterthan('createdat', oneweekago) we analyze the content to extract and count hashtags we sort by frequency to find the most popular hashtags handling search execution now let's look at how we handle the search execution based on the search type // from searchpage js search execution const handlesearch = (e) => { e preventdefault(); if (!searchquery trim()) return; switch (searchtype) { case 'users' searchusers(searchquery); break; case 'posts' searchposts(searchquery); break; case 'hashtags' searchhashtags(searchquery); break; default searchusers(searchquery); } }; optimizing search in back4app to optimize search performance in back4app create indexes navigate to your back4app dashboard go to "database browser" > select the class (e g , user, post) click on "indexes" tab create indexes for frequently searched fields for user class create an index on username for post class create an index on content use query constraints always use limit() to restrict the number of results use select() to only fetch the fields you need use skip() for pagination when dealing with large result sets consider cloud functions for complex searches for more complex search logic, implement a cloud function this allows you to perform server side processing and return optimized results example cloud function for advanced search // example cloud function for advanced search parse cloud define("advancedsearch", async (request) => { const { query, type, limit = 20 } = request params; if (!query) { throw new error("search query is required"); } let results = \[]; switch (type) { case 'users' const userquery = new parse query(parse user); userquery matches('username', new regexp(query, 'i')); userquery limit(limit); results = await userquery find({ usemasterkey true }); break; case 'posts' const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(query, 'i')); postquery include('author'); postquery limit(limit); results = await postquery find({ usemasterkey true }); break; // add more search types as needed default throw new error("invalid search type"); } return results; }); step 10 — testing and deploying your social network in this final step, we'll cover how to test your application, prepare it for production, deploy it to a hosting service, and monitor and scale your back4app backend these steps are crucial to ensure your social network runs smoothly in a production environment testing your application before deploying, it's important to thoroughly test your application to catch any bugs or issues 1\ manual testing create a test plan that covers all the key features of your application user authentication test registration with valid and invalid inputs test login with correct and incorrect credentials test password reset functionality test session persistence and logout post functionality test creating posts with text and images test viewing posts in the feed test liking and commenting on posts test deleting posts social interactions test viewing user profiles test commenting on posts test real time messaging search functionality test searching for users test searching for posts test hashtag search cross browser testing test on chrome, firefox, safari, and edge test on mobile browsers 2\ automated testing for more robust testing, implement automated tests // example jest test for the login component import react from 'react'; import { render, fireevent, waitfor } from '@testing library/react'; import loginpage from ' /src/pages/loginpage'; import parse from 'parse/dist/parse min js'; // mock parse jest mock('parse/dist/parse min js', () => ({ user { login jest fn() } })); test('login form submits with username and password', async () => { parse user login mockresolvedvalueonce({ id '123', get () => 'testuser' }); const { getbylabeltext, getbyrole } = render(\<loginpage />); // fill in the form fireevent change(getbylabeltext(/username/i), { target { value 'testuser' } }); fireevent change(getbylabeltext(/password/i), { target { value 'password123' } }); // submit the form fireevent click(getbyrole('button', { name /log in/i })); // check if parse user login was called with correct arguments await waitfor(() => { expect(parse user login) tohavebeencalledwith('testuser', 'password123'); }); }); 3\ back4app testing test your back4app configuration cloud functions test all cloud functions with various inputs security verify that your class level permissions are working correctly livequery test real time functionality with multiple clients preparing for production before deploying, optimize your application for production 1\ environment configuration create separate environment files for development and production \# env development react app parse app id=your dev app id react app parse js key=your dev js key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your dev app back4app io \# env production react app parse app id=your production app id react app parse js key=your production js key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your prod app back4app io 2\ build optimization optimize your react build // in your package json "scripts" { "analyze" "source map explorer 'build/static/js/ js'", "build" "generate sourcemap=false react scripts build" } install the source map explorer to analyze your bundle size npm install save dev source map explorer 3\ performance optimization implement code splitting to reduce initial load time // in app js, use react lazy for route components import react, { suspense, lazy } from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider } from '@chakra ui/react'; import loadingspinner from ' /components/loadingspinner'; // lazy load pages const landingpage = lazy(() => import(' /pages/landingpage')); const loginpage = lazy(() => import(' /pages/loginpage')); const feedpage = lazy(() => import(' /pages/feedpage')); // other pages function app() { return ( \<chakraprovider> \<router> \<suspense fallback={\<loadingspinner />}> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/feed" element={\<feedpage />} /> {/ other routes /} \</routes> \</suspense> \</router> \</chakraprovider> ); } deploying to a hosting service there are several options for deploying your react application 1\ deploying to vercel vercel is a great option for react applications install the vercel cli npm install g vercel deploy your application vercel for production deployment vercel prod 2\ deploying to netlify netlify is another excellent option install the netlify cli npm install g netlify cli build your application npm run build deploy to netlify netlify deploy for production deployment netlify deploy prod 3\ deploying to github pages for a simple deployment option install gh pages npm install save dev gh pages add to package json "homepage" "https //yourusername github io/your repo name", "scripts" { "predeploy" "npm run build", "deploy" "gh pages d build" } deploy npm run deploy monitoring and scaling your back4app backend as your social network grows, you'll need to monitor and scale your back4app backend 1\ monitoring performance back4app provides several tools to monitor your application dashboard analytics monitor api requests, storage usage, and file operations logs check server logs for errors and performance issues performance metrics track response times and identify bottlenecks to access these tools go to your back4app dashboard navigate to "analytics" for usage statistics check "logs" for detailed operation logs 2\ scaling your backend when your user base grows, you may need to scale your back4app backend upgrade your plan move to a higher tier plan with more resources optimize queries use indexes and limit queries to improve performance implement caching use client side caching for frequently accessed data 3\ database optimization optimize your database for better performance create indexes add indexes to frequently queried fields // example creating an index on the 'username' field in the user class const schema = new parse schema(' user'); schema addindex('username index', { username 1 }); schema update(); use aggregation pipeline for complex data operations // example count posts by user const pipeline = \[ { group { objectid '$author', count { $sum 1 } } } ]; const results = await parse cloud aggregate('post', pipeline); 4\ implementing a cdn for media for faster image and media delivery configure a cdn like cloudflare or amazon cloudfront update your back4app file storage settings to use the cdn update file urls in your application to use the cdn domain 5\ setting up monitoring alerts set up alerts to be notified of issues go to your back4app dashboard navigate to "app settings" > "alerts" configure alerts for high api usage error rate spikes database size limits server downtime summary of what was accomplished throughout this tutorial, you've set up a robust backend infrastructure created a back4app account and configured your application designed a database schema for users, posts, comments, and messages configured security settings and class level permissions set up livequery for real time functionality built a modern react frontend created a responsive ui with chakra ui components implemented client side routing with react router developed reusable components for posts, comments, and user profiles connected your frontend to back4app using the parse javascript sdk implemented core social network features user authentication (signup, login, password reset) post creation and interaction (likes, comments) user profiles and settings real time messaging between users search functionality for users, posts, and hashtags optimized for production implemented performance optimizations like code splitting set up environment configurations for development and production learned how to deploy your application to hosting services explored monitoring and scaling strategies for your back4app backend you now have a solid foundation for a social network application that can be extended and customized to meet your specific needs next steps for extending the application here are some exciting ways to enhance your social network application advanced media features add support for video uploads and playback implement image filters and editing tools create stories or ephemeral content features add support for gifs and other rich media enhanced social interactions implement a recommendation engine for friend suggestions add groups or communities functionality create events with rsvp capabilities develop a notification system for all user interactions monetization options implement premium membership features add in app purchases for digital goods create a marketplace for user to user transactions integrate with payment processors like stripe mobile experience convert your application to a progressive web app (pwa) develop native mobile apps using react native implement push notifications for mobile devices optimize the ui for different screen sizes and orientations analytics and insights integrate analytics tools to track user engagement create dashboards for content performance implement a/b testing for new features develop user behavior insights to improve the platform content moderation implement automated content filtering create reporting systems for inappropriate content develop admin tools for content moderation use machine learning for intelligent content analysis additional resources for learning to continue expanding your knowledge and skills, here are some valuable resources back4app documentation and tutorials back4app documentation https //www back4app com/docs/get started/welcome parse javascript guide https //docs parseplatform org/js/guide/ back4app youtube channel https //www youtube com/c/back4app react and modern javascript react documentation https //reactjs org/docs/getting started html javascript info https //javascript info/ egghead io react courses https //egghead io/q/react ui and ux design chakra ui documentation https //chakra ui com/docs/getting started ui design patterns https //ui patterns com/ nielsen norman group ux research https //www nngroup com/articles/ performance optimization web dev performance https //web dev/performance scoring/ react performance optimization https //reactjs org/docs/optimizing performance html google pagespeed insights https //developers google com/speed/pagespeed/insights/ community and support stack overflow https //stackoverflow\ com/questions/tagged/parse platform parse community forum https //community parseplatform org/ react dev community https //dev to/t/react remember that building a successful social network is an iterative process start with a solid foundation (which you now have), gather user feedback, and continuously improve your application based on real world usage patterns we hope this tutorial has provided you with the knowledge and confidence to build amazing applications with react and back4app happy coding