Project Templates
Social Network
리액트를 이용한 소셜 네트워크 구축 방법
119 분
소개 이 튜토리얼에서는 instagram과 유사한 완전한 기능을 갖춘 소셜 네트워크 플랫폼인 back4gram을 구축할 것입니다 back4gram은 사용자가 계정을 만들고, 이미지가 포함된 게시물을 공유하고, 좋아요 및 댓글을 통해 상호작용하며, 콘텐츠를 검색하고, 실시간 메시징을 통해 소통할 수 있도록 합니다 이 프로젝트는 react의 강력한 프론트엔드 기능과 back4app의 견고한 백엔드 서비스를 결합하여 현대적이고 기능이 풍부한 소셜 애플리케이션을 만드는 방법을 보여줍니다 react는 재사용 가능한 ui 요소와 효율적인 렌더링을 가능하게 하는 구성 요소 기반 아키텍처 덕분에 소셜 네트워크의 프론트엔드에 완벽한 선택입니다 한편, back4app은 사용자 인증, 데이터 저장, 파일 업로드 및 실시간 기능을 처리하는 관리형 parse server 백엔드를 제공하여 복잡한 서버 인프라를 처음부터 구축할 필요가 없습니다 이 튜토리얼을 완료하면 다음과 같은 완전한 소셜 네트워크를 구축하게 됩니다 사용자 인증 (가입, 로그인, 비밀번호 재설정) 프로필 관리 이미지 업로드가 포함된 게시물 작성 소셜 상호작용 (좋아요, 댓글) 타이핑 표시가 있는 실시간 메시징 콘텐츠 검색 기능 사용자 설정 및 선호도 그 과정에서 다음과 같은 귀중한 기술을 습득하게 됩니다 훅과 컨텍스트를 사용한 react 개발 chakra ui를 사용한 ui 개발 back4app을 통한 parse server 통합 livequery를 통한 실시간 데이터 관리 사용자 인증 흐름 파일 업로드 처리 반응형 디자인 구현 자신만의 소셜 플랫폼을 시작하려고 하든, 현대 소셜 네트워크가 어떻게 구축되는지 이해하고 싶든, 이 튜토리얼은 목표를 달성하는 데 필요한 지식과 실무 경험을 제공할 것입니다 언제든지 github 에서 전체 코드를 확인할 수 있습니다 전제 조건 이 튜토리얼을 완료하려면 다음이 필요합니다 back4app 계정 무료 계정에 가입하세요 back4app com https //www back4app com/ 이것을 사용하여 백엔드 서비스를 생성하고 관리할 것입니다 로컬 머신에 node js와 npm이 설치되어 있습니다 node js (버전 14 x 이상) 및 npm을 다음에서 설치하십시오 nodejs org https //nodejs org/ 설치를 확인하려면 다음을 실행하세요 node v 및 npm v 터미널에서 리액트에 대한 기본 이해 react 컴포넌트, 훅, 및 jsx에 대한 친숙함 react에 대한 지식을 보충해야 한다면, 다음을 확인하세요 공식 react 문서 https //reactjs org/docs/getting started html 코드 편집기 비주얼 스튜디오 코드, 서브라임 텍스트 또는 아톰과 같은 현대적인 코드 편집기 git (선택 사항) 버전 관리를 위해 및 리포지토리를 따라가기 위해 보조 자료 back4app 문서 https //www back4app com/docs/get started/welcome 파스 자바스크립트 가이드 https //docs parseplatform org/js/guide/ chakra ui 문서 https //chakra ui com/docs/getting started 리액트 라우터 문서 https //reactrouter com/en/main 1단계 — back4app 백엔드 설정하기 이 단계에서는 새로운 back4app 프로젝트를 생성하고 소셜 네트워크 애플리케이션에 필요한 데이터베이스 스키마를 구성합니다 back4app은 사용자 인증, 데이터 저장 및 실시간 기능을 처리할 관리형 parse server를 제공합니다 새로운 back4app 프로젝트 만들기 back4app 계정에 로그인하고 대시보드로 이동합니다 "새 앱 만들기" 버튼을 클릭합니다 앱 이름으로 "back4gram"을 입력하고 가장 가까운 서버 지역을 선택한 후 "생성"을 클릭합니다 앱이 생성되면 앱 대시보드로 리디렉션됩니다 데이터베이스 스키마 이해하기 데이터베이스에 클래스를 생성하기 전에, 우리의 소셜 네트워크에 필요한 데이터 모델을 이해해 봅시다 애플리케이션 요구 사항에 따라, 다음과 같은 클래스가 필요합니다 사용자 (기본적으로 parse에 이미 존재함) 이 클래스는 사용자 인증 및 프로필 정보를 처리합니다 우리는 약력 및 아바타와 같은 추가 필드로 확장할 것입니다 게시물 사용자 게시물을 텍스트 콘텐츠와 이미지로 저장합니다 필드 내용 (문자열), 작성자 (사용자에 대한 포인터), 이미지 (파일), 좋아요 (숫자), 댓글 (배열), 생성일 (날짜) 댓글 게시물에 대한 댓글 저장 필드 내용 (문자열), 작성자 (사용자에 대한 포인터), 게시물 (게시물에 대한 포인터), 생성일 (날짜) 대화 사용자 간의 채팅 대화를 나타냅니다 필드 참가자 (사용자에 대한 포인터 배열), 마지막 메시지 (문자열), 업데이트 날짜 (날짜) 메시지 대화 내 개별 메시지 필드 텍스트 (문자열), 발신자 (사용자에 대한 포인터), 대화 (대화에 대한 포인터), 생성일 (날짜) 타이핑 상태 (실시간 타이핑 표시를 위한) 사용자가 대화 중에 입력할 때 추적합니다 필드 사용자 (사용자에 대한 포인터), 대화 (대화에 대한 포인터), 입력 중 (부울) 데이터베이스 클래스 만들기 이제 back4app 데이터베이스에서 이러한 클래스를 만들어 보겠습니다 back4app 대시보드에서 "데이터베이스" 섹션으로 이동하세요 사용자 클래스 확장 이미 존재하는 "사용자" 클래스를 클릭하세요 다음 열을 추가하세요 바이오 (유형 문자열) 아바타 (유형 파일) 팔로워 (유형 숫자, 기본값 0) 다음 (유형 숫자, 기본값 0) 게시물 클래스 만들기 "수업 만들기"를 클릭하세요 "post"를 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하세요 내용 (유형 문자열) 저자 (유형 user에 대한 포인터) 이미지 (유형 파일) 좋아요 (유형 숫자, 기본값 0) 댓글 (유형 배열) 생성일 (유형 날짜, 자동 추가됨) 댓글 클래스 만들기 "수업 만들기"를 클릭하세요 "주석"을 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하십시오 내용 (유형 문자열) 저자 (유형 user에 대한 포인터) 게시물 (유형 게시물에 대한 포인터) 생성일 (유형 날짜, 자동 추가됨) 대화 클래스 만들기 "수업 만들기"를 클릭하세요 "대화"를 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하세요 참가자 (유형 배열) 마지막 메시지 (유형 문자열) 업데이트됨 (유형 날짜, 자동으로 추가됨) 메시지 클래스 생성 "수업 만들기"를 클릭하세요 "메시지"를 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하세요 텍스트 (유형 문자열) 발신자 (유형 user에 대한 포인터) 대화 (유형 대화에 대한 포인터) 생성일 (유형 날짜, 자동으로 추가됨) typingstatus 클래스를 생성하는 중 "수업 만들기"를 클릭하세요 "typingstatus"를 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하세요 사용자 (유형 user에 대한 포인터) 대화 (유형 대화에 대한 포인터) 입력 중 (유형 불리언) 클래스 권한 설정 (선택 사항) 데이터 보안을 보장하기 위해 각 클래스에 대해 적절한 접근 제어 목록(acl)을 구성해야 합니다 back4app 대시보드에서 "보안 및 키" 섹션으로 이동하세요 "클래스 수준 보안"에서 다음 권한을 구성하십시오 사용자 클래스 공개 읽기 접근 활성화됨 (사용자가 다른 사용자의 프로필을 볼 수 있도록) 공용 쓰기 접근 비활성화 (사용자는 자신의 프로필만 수정할 수 있습니다) 수업 게시물 공개 읽기 접근 활성화됨 (모두가 게시물을 볼 수 있음) 공개 쓰기 접근 활성화됨 (인증된 사용자가 게시물을 생성할 수 있음) 업데이트/삭제를 위한 clp를 추가하여 작성자만 제한합니다 주석 클래스 공개 읽기 접근 활성화됨 (모두가 댓글을 볼 수 있음) 공개 쓰기 접근 활성화됨 (인증된 사용자가 댓글을 작성할 수 있음) 업데이트/삭제를 위해 저자에게만 제한하는 clp 추가 대화 수업 공개 읽기 접근 비활성화됨 (대화는 비공개입니다) 공개 쓰기 접근 활성화됨 (인증된 사용자가 대화를 생성할 수 있음) 대화 참가자에 대한 읽기/쓰기 접근을 제한하는 clp 추가 메시지 클래스 공개 읽기 접근 비활성화됨 (메시지는 비공개입니다) 공용 쓰기 접근 활성화됨 (인증된 사용자가 메시지를 보낼 수 있음) 대화 참가자에 대한 읽기/쓰기 접근을 제한하는 clp 추가 타이핑 상태 클래스 공개 읽기 접근 비활성화 (입력 상태는 비공개입니다) 공용 쓰기 접근 활성화됨 (인증된 사용자가 입력 상태를 업데이트할 수 있음) 대화 참가자에 대한 읽기/쓰기 접근을 제한하는 clp 추가 실시간 기능을 위한 livequery 설정 메시징 및 입력 표시와 같은 실시간 기능을 활성화하려면 livequery를 구성해야 합니다 back4app 대시보드에서 "서버 설정" 섹션으로 이동합니다 "parse server" 아래에서 "livequery" 섹션을 찾아 활성화합니다 livequery에서 모니터링할 다음 클래스를 추가합니다 메시지 입력 상태 게시물 (좋아요 및 댓글에 대한 실시간 업데이트용) 변경 사항을 저장하세요 애플리케이션 키 가져오기 react 프론트엔드를 백엔드에 연결하려면 back4app 애플리케이션 키가 필요합니다 "앱 설정" > "보안 및 키" 섹션으로 이동하세요 다음 키를 기록하세요 애플리케이션 id javascript 키 서버 url livequery 서버 url (서브도메인 구성, 실시간 기능을 위한) 이 키들을 react 애플리케이션에서 parse를 초기화하는 데 사용할 것입니다 2단계 — react 프론트엔드 프로젝트 생성 이 단계에서는 새로운 react 프로젝트를 설정하고 back4app 백엔드와 함께 작동하도록 구성합니다 필요한 종속성을 설치하고, 프로젝트 구조를 만들고, parse 서버에 연결합니다 새로운 react 프로젝트 설정하기 create react app을 사용하여 새로운 react 애플리케이션을 만드는 것으로 시작하겠습니다 이는 구성 없이 현대적인 빌드 설정을 제공합니다 터미널을 열고 프로젝트를 만들고자 하는 디렉토리로 이동합니다 새로운 react 애플리케이션을 만들기 위해 다음 명령어를 실행합니다 npx create react app back4gram 프로젝트가 생성되면 프로젝트 디렉토리로 이동합니다 cd back4gram 개발 서버를 시작하여 모든 것이 작동하는지 확인합니다 npm start 이렇게 하면 브라우저에서 새로운 react 애플리케이션이 열립니다 http //localhost 3000 http //localhost 3000 필요한 종속성 설치하기 이제 소셜 네트워크 애플리케이션에 필요한 패키지를 설치하겠습니다 개발 서버를 중지합니다 (터미널에서 ctrl+c를 누르세요) back4app에 연결하기 위해 parse sdk를 설치합니다 npm install parse 내비게이션을 위해 react router를 설치합니다 npm install react router dom 사용자 인터페이스 구성 요소를 위해 chakra ui를 설치합니다 npm install @chakra ui/react @emotion/react @emotion/styled framer motion 추가 ui 유틸리티 및 아이콘 라이브러리를 설치합니다 npm install react icons 프로젝트 구조 설명 명확한 구조로 프로젝트를 정리합시다 다음 디렉토리를 src 폴더에 생성합니다 mkdir p src/components/ui src/pages src/contexts src/utils 각 디렉토리의 용도는 다음과 같습니다 components 재사용 가능한 ui 컴포넌트 ui 버튼, 폼, 모달과 같은 기본 ui 컴포넌트 특정 기능을 위한 다른 컴포넌트 폴더 (예 게시물, 댓글) pages 경로에 해당하는 전체 페이지 컴포넌트 contexts 상태 관리를 위한 react 컨텍스트 제공자 utils 유틸리티 함수 및 헬퍼 환경 변수 만들기 back4app 자격 증명을 안전하게 저장하려면, env 파일을 프로젝트의 루트에 생성하세요 프로젝트 루트에 env local 이라는 이름의 새 파일을 만드세요 touch env local 파일을 열고 back4app 자격 증명을 추가하세요 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 플레이스홀더 값을 1단계에서 얻은 실제 back4app 자격 증명으로 교체하세요 반드시 env local 을 gitignore 파일에 추가하여 민감한 정보가 커밋되지 않도록 하세요 back4app 자격 증명으로 parse sdk 구성하기 이제 parse sdk를 설정하여 back4app 백엔드에 연결해 보겠습니다 새 파일을 생성하세요 src/utils/parseconfig js // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // 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; // 라이브 쿼리 초기화 if (process env react app parse live query url) { parse livequeryserverurl = process env react app parse live query url; } export default parse; 당신의 src/index js 파일을 업데이트하여 parse 구성을 가져옵니다 import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import reportwebvitals from ' /reportwebvitals'; import ' /utils/parseconfig'; // parse 구성 가져오기 const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); reportwebvitals(); 라우팅이 포함된 앱 컴포넌트 설정 라우팅과 chakra ui 제공자를 포함하도록 주요 app 컴포넌트를 업데이트합시다 업데이트 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 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'; // 사용자 정의 테마 만들기 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; 보호된 경로 구성 요소 만들기 인증이 필요한 경로를 보호하기 위해, protectedroute 구성 요소를 만들어 보겠습니다 먼저, 사용자 인증 상태를 관리하기 위해 authcontext를 생성합니다 // 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>; } 이제 protectedroute 컴포넌트를 생성하세요 // 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; authprovider와 protectedroute를 사용하도록 app 구성 요소를 업데이트하세요 // 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; 기본 랜딩 페이지 만들기 설정을 테스트하기 위해 간단한 랜딩 페이지를 만들어 보겠습니다 // 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; 설정 테스트 이제 react 애플리케이션의 기본 구조를 설정하고 back4app에 연결했으니, 테스트해 보겠습니다 개발 서버 시작 npm start 브라우저를 열고 http //localhost 3000 http //localhost 3000 가입 또는 로그인 버튼이 있는 랜딩 페이지가 표시되어야 합니다 브라우저 콘솔을 확인하여 parse 초기화와 관련된 오류가 없는지 확인하세요 3단계 — 인증 기능 구현 이 단계에서는 back4app의 parse server를 사용하여 소셜 네트워크 애플리케이션의 사용자 인증 기능을 구현합니다 parse 인증 시스템이 어떻게 작동하는지 살펴보고 로그인, 가입 및 비밀번호 재설정 기능을 구현할 것입니다 parse 사용자 인증 시스템 이해하기 back4app의 parse server는 parse user 클래스를 통해 포괄적인 사용자 관리 시스템을 제공합니다 우리 애플리케이션에서 parse 인증이 어떻게 작동하는지 이해해 봅시다 parse user 클래스 이 parse user 클래스는 사용자 관리를 위해 특별히 설계된 parse object 의 특별한 하위 클래스입니다 우리의 back4gram 애플리케이션에서는 이를 사용하여 사용자 자격 증명 저장 (사용자 이름, 이메일, 비밀번호) 인증 상태 관리 세션 토큰 자동 처리 우리의 구현을 살펴보면, parse user 클래스와 어떻게 상호작용하는지 알 수 있습니다 // from signuppage js const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); await user signup(); 이 코드는 새로운 parse user 객체를 생성하고, 필수 필드를 설정한 후 signup() 메서드를 호출하여 back4app에 사용자를 등록합니다 parse에서의 인증 흐름 우리 애플리케이션에서 인증이 어떻게 작동하는지 살펴보겠습니다 등록 흐름 우리의 signuppage js에서는 사용자 이름, 이메일 및 비밀번호를 수집합니다 우리는 입력 데이터를 검증합니다 (빈 필드 확인, 유효한 이메일 형식, 비밀번호 길이) 우리는 새로운 parse user 객체를 생성하고 자격 증명을 설정합니다 우리는 signup()을 호출하여 데이터를 back4app에 보냅니다 비밀번호를 저장하기 전에 해시를 파싱합니다 성공하면 사용자가 세션 토큰으로 자동 로그인됩니다 로그인 흐름 우리의 loginpage js에서 우리는 사용자 이름과 비밀번호를 수집합니다 이 자격 증명으로 parse user login()을 호출합니다 파서는 저장된 데이터에 대해 자격 증명을 확인합니다 유효한 경우, parse는 세션 토큰을 생성합니다 세션 토큰은 브라우저 저장소에 자동으로 저장됩니다 세션 관리 파서는 자동으로 모든 api 요청에 세션 토큰을 포함합니다 현재 로그인한 사용자를 검색하기 위해 parse user current()를 사용합니다 세션은 페이지 새로 고침 간에 유지됩니다 사용자 등록 구현 사용자 등록이 어떻게 구현되는지 이해하기 위해 signuppage 구성 요소를 살펴보겠습니다 양식 유효성 검사 back4app에 데이터를 보내기 전에 사용자 입력을 검증합니다 // 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; }; 이 검증은 다음을 보장합니다 사용자 이름이 비어 있지 않음 유효한 이메일 비밀번호가 최소 6자 이상 비밀번호와 확인이 일치함 등록 오류 처리 우리의 가입 핸들러는 parse 특정 오류에 대한 오류 처리를 포함합니다 // 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은 사용자에게 유용한 피드백을 제공하기 위해 사용할 수 있는 특정 오류 코드를 반환합니다 코드 202 사용자 이름이 이미 사용 중입니다 코드 203 이메일이 이미 사용 중입니다 사용자 등록/가입에 대한 전체 코드는 여기에서 찾을 수 있습니다 사용자 로그인 구현하기 우리의 loginpage 컴포넌트는 parse user login()을 사용하여 사용자 인증을 처리합니다 로그인 양식 로그인 양식은 사용자 이름과 비밀번호를 수집합니다 // 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> 세션 검증 앞서 설명한 바와 같이, 로그인 페이지가 로드될 때 기존 세션을 확인합니다 // 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]); 이것은 parse의 주요 기능입니다 브라우저 저장소에서 세션 토큰을 자동으로 관리하여 사용자가 이미 로그인했는지 쉽게 확인할 수 있습니다 비밀번호 재설정 구현 back4app은 내장된 비밀번호 재설정 흐름을 제공합니다 우리 애플리케이션에서는 로그인 양식에서 비밀번호 재설정 페이지로 연결합니다 // from loginpage js \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> back4app의 비밀번호 재설정 프로세스는 다음과 같이 작동합니다 사용자가 이메일로 비밀번호 재설정을 요청합니다 parse가 사용자의 이메일로 특별한 재설정 링크를 보냅니다 사용자가 링크를 클릭하고 새 비밀번호를 설정합니다 parse가 데이터베이스에서 비밀번호 해시를 업데이트합니다 이것을 우리 애플리케이션에 구현하기 위해서는 다음을 사용합니다 // example password reset implementation try { await parse user requestpasswordreset(email); // show success message } catch (error) { // handle error } 인증된 사용자를 위한 경로 보호 우리 애플리케이션의 특정 경로를 보호하기 위해, 사용자가 인증되었는지 확인하는 protectedroute 컴포넌트를 사용합니다 // 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; } 이 컴포넌트는 사용자가 로그인했는지 확인하기 위해 authcontext를 사용합니다 확인하는 동안 로딩 스피너를 표시합니다 사용자가 발견되지 않으면 로그인 페이지로 리디렉션합니다 사용자가 인증되면 보호된 콘텐츠를 렌더링합니다 이 구성 요소를 라우팅 설정에서 사용합니다 // from app js \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> back4app 인증 구성 back4app은 대시보드에서 인증을 위한 여러 구성 옵션을 제공합니다 이메일 확인 사용자가 로그인하기 전에 이메일 확인을 요구할 수 있습니다 이것을 "서버 설정" > "파스 서버" > "사용자 인증"에서 구성합니다 비밀번호 정책 최소 비밀번호 길이 및 복잡성 요구 사항 설정 이것을 "서버 설정" > "파스 서버" > "사용자 인증"에서 구성합니다 세션 길이 사용자 세션이 유효한 기간을 제어합니다 이것을 "서버 설정" > "파스 서버" > "세션 구성"에서 구성합니다 이메일 템플릿 확인 및 비밀번호 재설정 이메일 사용자 정의 이것을 "앱 설정" > "이메일 템플릿"에서 구성합니다 인증 구현 테스트 인증 시스템이 올바르게 작동하는지 확인하려면 사용자 등록 테스트 유효한 자격 증명으로 등록 시도 기존 사용자 이름으로 등록 시도 (오류가 표시되어야 함) 사용자가 " user" 클래스 아래 back4app 대시보드에 나타나는지 확인 사용자 로그인 테스트 올바른 자격 증명으로 로그인 시도 (피드로 리디렉션되어야 함) 잘못된 자격 증명으로 로그인 시도 (오류가 표시되어야 함) 세션 지속성 테스트 로그인하고 페이지 새로 고침 (로그인 상태 유지해야 함) 브라우저를 닫고 다시 열기 (세션이 유효하면 로그인 상태 유지해야 함) 보호된 경로 테스트 로그아웃 상태에서 /feed 접근 시도 (로그인 페이지로 리디렉션되어야 함) 로그인 상태에서 /feed 접근 시도 (피드 페이지가 표시되어야 함) 로그인 컴포넌트의 코드는 여기에서 찾을 수 있습니다 4단계 — 피드 기능 개발 이 단계에서는 핵심 소셜 네트워킹 기능인 피드를 구현합니다 여기에서 사용자는 게시물을 작성하고, 다른 사람의 콘텐츠를 보고, 좋아요 및 댓글을 통해 상호작용합니다 우리는 back4app의 parse server를 사용하여 게시물을 저장하고 검색하며, 이미지 파일 업로드를 처리하고, 실시간 업데이트를 구현할 것입니다 피드 페이지 구조 이해하기 우리 애플리케이션의 피드 페이지는 세 가지 주요 구성 요소로 이루어져 있습니다 탐색을 위한 사이드바 게시물 생성 및 게시물 목록이 있는 주요 피드 영역 트렌딩 섹션 (더 큰 화면에서) 이것이 우리 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> ); } 이 반응형 레이아웃은 다양한 화면 크기에 적응하며, 모바일 장치에서는 사이드바를 숨깁니다 back4app에서 포스트 클래스를 생성하기 프론트엔드를 구현하기 전에, back4app 데이터베이스가 포스트를 위해 제대로 설정되었는지 확인합시다 post 클래스는 다음 필드를 가져야 합니다 내용 (문자열) 게시물의 텍스트 내용 이미지 (파일) 선택적 이미지 첨부 작성자 (사용자에 대한 포인터) 게시물을 작성한 사용자 좋아요 (숫자) 게시물의 좋아요 수 좋아요한 사용자 (배열) 게시물을 좋아한 사용자 id의 배열 생성일 (날짜) parse에 의해 자동으로 추가됨 post 클래스에 적절한 권한 설정 공개 읽기 접근 모든 사용자가 게시물을 읽을 수 있어야 함 공개 쓰기 접근 인증된 사용자가 게시물을 작성할 수 있어야 함 수정/삭제 권한 오직 작성자만 자신의 게시물을 수정할 수 있어야 함 게시물 생성 구현 우리의 feedpage 구성 요소에서 게시물 생성이 어떻게 구현되는지 살펴보겠습니다 // 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); } }; 게시물 생성에 대한 주요 사항 parse에서 파일 처리 parse file은 back4app의 저장소에 이미지를 업로드하는 데 사용됩니다 파일이 먼저 저장된 후 게시물 객체에 첨부됩니다 back4app은 파일 저장을 자동으로 처리하고 url을 생성합니다 parse 객체 생성 우리는 'post' 클래스를 확장합니다 parse object extend('post') 우리는 새로운 인스턴스를 생성합니다 new post() 우리는 set() 메서드를 사용하여 속성을 설정합니다 우리는 save() 사용자 연관 우리는 현재 사용자와 게시물을 연관시킵니다 parse user current() 이것은 데이터베이스에서 포인터 관계를 생성합니다 게시물 생성 양식 ui는 다음과 같습니다 {/ 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> 게시물 가져오기 및 표시하기 이제 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(); }, \[]); 게시물 가져오기에 대한 주요 사항 쿼리 파싱 우리는 new parse query(post) 우리는 관련 객체를 포함합니다 query include('author') 우리는 정렬합니다 query descending('createdat') 우리는 페이지 매김합니다 query limit() 및 query skip() 우리는 쿼리를 실행합니다 query find() 결과 처리 parse 객체는 속성에 접근하기 위한 get() 메서드를 가지고 있습니다 파일 필드의 경우, 우리는 file url() 를 사용하여 url을 가져옵니다 우리는 parse 객체를 react 상태를 위한 일반 javascript 객체로 변환합니다 페이지 매김 우리는 페이지 추적을 통해 "더 불러오기" 기능을 구현합니다 우리는 추가 요청을 하기 전에 더 불러올 게시물이 있는지 확인합니다 게시물은 목록에 표시됩니다 {/ 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> )} 좋아요 기능 구현하기 좋아요 기능이 어떻게 구현되는지 살펴보겠습니다 // 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', }); } }; 좋아요 기능에 대한 주요 사항 낙관적 업데이트 서버가 변경 사항을 확인하기 전에 ui를 즉시 업데이트합니다 이로 인해 앱이 더 반응성이 느껴집니다 파싱 객체 업데이트 특정 게시물을 가져옵니다 query get(postid) 속성을 수정합니다 post set() 변경 사항을 저장합니다 post save() 좋아요 추적 우리는 카운트 ( 좋아요 )와 사용자 목록 ( likedby )를 유지합니다 이것은 우리가 정확한 카운트를 표시하고 현재 사용자가 게시물을 좋아했는지 여부를 판단할 수 있게 해줍니다 실시간 업데이트 구현하기 (선택 사항) 새 게시물이 생성될 때 피드를 실시간으로 업데이트하려면 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(); } }; }, \[]); livequery에 대한 주요 사항 구독 설정 쿼리를 생성하고 query subscribe() 이것은 back4app의 livequery 서버에 websocket 연결을 설정합니다 이벤트 처리 새 게시물이 생성될 때 'create' 이벤트를 수신합니다 게시물이 수정될 때 'update' 이벤트를 수신합니다 그에 따라 로컬 상태를 업데이트합니다 정리 메모리 누수를 방지하기 위해 컴포넌트가 언마운트될 때 구독을 취소합니다 페이지네이션을 통한 게시물 로딩 최적화 우리는 이미 "더 불러오기" 버튼으로 기본 페이지네이션을 구현했습니다 무한 스크롤로 이를 향상시켜 보겠습니다 // 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); } }; 그리고 게시물 목록의 끝에 이것을 추가하세요 {/ infinite scroll trigger /} {hasmore && ( \<box id="load more trigger" h="20px" /> )} 무한 스크롤에 대한 주요 사항 교차 관찰자 사용자가 스크롤을 하여 바닥에 도달할 때를 감지하기 위해 교차 관찰자 api를 사용합니다 트리거 요소가 보이게 되면, 더 많은 게시물을 로드합니다 로딩 상태 초기 로드와 "더 로드"에 대해 별도의 로딩 상태를 추적합니다 이것은 여러 개의 동시 요청을 방지합니다 성능 고려 사항 한 번에 고정된 수의 게시물만 로드합니다 (페이지네이션) 추가 요청을 하기 전에 로드할 더 많은 게시물이 있는지 확인합니다 back4app 성능 최적화 back4app에서 작업할 때 성능을 최적화하려면 인덱스 사용 back4app 대시보드에서 자주 쿼리되는 필드에 인덱스를 추가하세요 post 클래스의 경우, 'createdat' 및 'author'에 인덱스를 추가하세요 선택적 쿼리 필요한 필드만 가져오려면 query select() 를 사용하세요 이것은 데이터 전송을 줄이고 성능을 향상시킵니다 카운트 최적화 모든 게시물을 가져와서 카운트하는 대신, query count() 이것은 총 카운트를 결정하는 데 더 효율적입니다 6단계 — 소셜 상호작용 추가 이 단계에서는 댓글, 사용자 프로필 및 사용자 설정과 같은 주요 소셜 상호작용 기능을 구현하여 소셜 네트워크를 강화할 것입니다 이러한 기능이 back4app 백엔드와 어떻게 상호작용하는지, 그리고 이를 작동시키는 메커니즘에 초점을 맞출 것입니다 게시물에 대한 댓글 구현 댓글은 back4app에서 적절한 데이터 모델링이 필요한 기본적인 소셜 상호작용 기능입니다 댓글을 구현하기 위해 우리 애플리케이션이 parse server와 어떻게 상호작용하는지 살펴보겠습니다 댓글을 위한 back4app 데이터 모델 back4app에서 댓글은 사용자와 게시물 모두와의 관계를 가진 별도의 클래스로 구현됩니다 댓글 클래스 구조 내용 (문자열) 댓글의 텍스트 내용 작성자 (사용자에 대한 포인터) 댓글을 작성한 사용자를 가리킴 게시물 (게시물에 대한 포인터) 댓글이 달린 게시물을 가리킴 생성일 (날짜) parse에 의해 자동으로 관리됨 관계 유형 사용자 → 댓글 일대다 (한 사용자가 여러 댓글을 작성할 수 있음) 게시물 → 댓글 일대다 (한 게시물에 여러 댓글이 있을 수 있음) back4app에서 댓글 가져오기 우리의 postdetailspage는 특정 게시물에 대한 댓글을 가져오기 위해 parse 쿼리를 사용합니다 // 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', }); } }; back4app의 주요 메커니즘 parse object extend() back4app에서 comment 클래스에 대한 참조를 생성합니다 query equalto() 특정 게시물에 대한 댓글만 찾기 위한 제약 조건을 생성합니다 query include() 단일 쿼리에서 관련 객체를 가져오기 위해 조인과 유사한 작업을 수행합니다 query descending() 특정 필드에 따라 결과를 정렬합니다 back4app에서 댓글 생성하기 사용자가 댓글을 추가할 때, 우리는 새로운 parse 객체를 생성하고 관계를 설정합니다 // 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() 댓글 클래스의 새 인스턴스를 생성합니다 comment set() 관련 객체에 대한 포인터를 포함하여 parse 객체의 속성을 설정합니다 comment save() 객체를 back4app에 저장하기 위해 전송합니다 parse user current() 현재 인증된 사용자를 가져와서 작성자 관계를 설정합니다 back4app 댓글 보안 back4app에서 댓글을 적절히 보호하려면 클래스 수준 권한 설정 (clps) 읽기 공개 (모두가 댓글을 읽을 수 있음) 쓰기 인증된 사용자만 (로그인한 사용자만 댓글을 달 수 있음) 업데이트/삭제 작성자만 (댓글 작성자만 수정 또는 삭제할 수 있음) 이 권한을 back4app 대시보드에서 설정하세요 { "find" { " " true }, "get" { " " true }, "create" { " " true }, "update" { "requiresauthentication" true }, "delete" { "requiresauthentication" true }, "addfield" { "requiresauthentication" true } } 7단계 back4app을 사용한 사용자 프로필 구축 우리 애플리케이션의 사용자 프로필은 parse의 내장 사용자 클래스를 사용자 정의 필드와 함께 활용합니다 profilepage js가 back4app과 어떻게 상호작용하는지 살펴보겠습니다 back4app 사용자 클래스 확장 parse 사용자 클래스는 우리의 소셜 네트워크를 위해 추가 필드로 확장됩니다 사용자 정의 사용자 필드 아바타 (파일) back4app의 파일 저장소에 저장된 프로필 사진 약력 (문자열) 사용자 약력 웹사이트 (문자열) 사용자의 웹사이트 url 표시 이름 (문자열) 사용자의 표시 이름 사용자 데이터 및 게시물 가져오기 우리의 프로필 페이지는 사용자 데이터와 사용자의 게시물을 모두 가져옵니다 // 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); } }; back4app의 주요 메커니즘 parse user current() 세션 토큰에서 인증된 사용자 검색 query equalto('author', currentuser) 현재 사용자의 게시물을 찾기 위한 포인터 동등성 제약 조건 생성 post get('image') url() back4app에 저장된 parse 파일 객체의 url에 접근 사용자 설정 구현 설정 페이지는 사용자가 프로필 정보를 업데이트하고 계정 설정을 관리할 수 있도록 합니다 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> ); } back4app의 주요 메커니즘 parse user current() 현재 사용자를 가져와 설정을 업데이트합니다 currentuser set() parse user 객체에서 사용자 속성을 업데이트합니다 currentuser save() 변경 사항을 back4app에 저장합니다 back4app 사용자 설정 스키마 back4app에서 설정을 구현하려면 다음 필드를 user 클래스에 추가하세요 privacysettings (객체) 개인 정보 선호도를 포함하는 json 객체 securitysettings (객체) 보안 설정을 포함하는 json 객체 notificationsettings (객체) 알림 선호도를 포함하는 json 객체 이 객체에 대한 예제 스키마 // privacysettings { "profilevisibility" "public", // 또는 "friends" 또는 "private" "postprivacy" "friends", // 또는 "public" 또는 "private" "showactivity" true } // securitysettings { "twofactorauth" false, "loginalerts" true } // notificationsettings { "likes" true, "comments" true, "follows" true, "messages" true } 소셜 상호작용을 위한 back4app 클라우드 함수 더 복잡한 사회적 상호작용을 위해, back4app에서 cloud functions를 구현할 수 있습니다 예를 들어, 댓글 알림을 추적하기 위해 // 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 }); }); 이를 구현하려면 back4app 대시보드로 이동하세요 "클라우드 코드" > "클라우드 함수"로 이동하세요 위의 코드로 새 함수를 만드세요 함수를 배포하세요 8단계 — 실시간 메시징 구축 이 단계에서는 back4app의 livequery 기능을 사용하여 실시간 메시징 기능을 구현할 것입니다 이를 통해 사용자는 페이지를 새로 고치지 않고도 즉시 메시지를 교환할 수 있어, 인기 있는 메시징 플랫폼과 유사한 동적인 채팅 경험을 제공합니다 back4app livequery 이해하기 구현에 들어가기 전에 back4app의 livequery가 어떻게 작동하는지 이해해 봅시다 라이브 쿼리란 무엇인가요? livequery는 클라이언트가 쿼리에 구독할 수 있도록 하는 parse server의 기능입니다 이 쿼리에 일치하는 객체가 변경되면, 서버는 자동으로 구독한 클라이언트에게 업데이트를 푸시합니다 이것은 복잡한 websocket 처리를 직접 구현하지 않고도 실시간 기능을 생성합니다 라이브 쿼리 작동 방식 livequery는 클라이언트와 서버 간의 websocket 연결을 설정합니다 클라이언트는 모니터링하고 싶은 특정 쿼리에 구독합니다 이 쿼리에 일치하는 데이터가 변경되면 서버는 websocket을 통해 이벤트를 전송합니다 클라이언트는 이러한 이벤트를 수신하고 ui를 그에 따라 업데이트합니다 라이브 쿼리 이벤트 생성하다 쿼리에 일치하는 새로운 객체가 생성될 때 트리거됩니다 업데이트 쿼리와 일치하는 기존 객체가 업데이트될 때 트리거됩니다 입력 쿼리와 일치하는 객체가 시작될 때 트리거됨 떠나다 쿼리와 더 이상 일치하지 않을 때 트리거됨 삭제 쿼리와 일치하는 객체가 삭제될 때 트리거됩니다 back4app에서 livequery 설정하기 애플리케이션에 livequery를 활성화하려면 다음 단계를 따르세요 back4app 서브도메인 활성화 back4app 계정에 로그인하세요 "앱 설정" > "서버 설정"으로 이동하십시오 "서버 url 및 실시간 쿼리" 블록을 찾아서 "설정"을 클릭하세요 "back4app 서브도메인 활성화" 옵션을 확인하세요 이 서브도메인은 귀하의 livequery 서버로 사용됩니다 라이브 쿼리 활성화 같은 설정 페이지에서 "실시간 쿼리 활성화" 옵션을 확인하세요 livequery로 모니터링할 수업을 선택하세요 메시지 (채팅 메시지용) 타이핑 상태 (타이핑 표시용) 대화 (대화 업데이트용) 변경 사항을 저장하세요 라이브 쿼리 서버 url을 기록하세요 귀하의 livequery 서버 url은 다음 형식입니다 wss\ //yourappname back4app io 이 url은 react 애플리케이션에서 livequery 클라이언트를 초기화하는 데 필요합니다 리액트 앱에서 livequery 구성하기 리액트 애플리케이션에서 livequery를 사용하려면 livequery 클라이언트를 초기화해야 합니다 // 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' 당신의 env local 파일에 다음을 포함해야 합니다 react app parse live query url=wss\ //yourappname back4app io 메시징을 위한 데이터 모델 생성 우리의 메시징 시스템은 back4app에서 두 가지 주요 클래스를 요구합니다 대화 수업 참가자 (배열) 대화에 있는 사용자에 대한 사용자 포인터의 배열 마지막 메시지 (문자열) 가장 최근 메시지의 내용 마지막 메시지 날짜 (날짜) 가장 최근 메시지의 타임스탬프 업데이트됨 (날짜) parse에 의해 자동으로 관리됨 메시지 클래스 대화 (포인터) 이 메시지가 속한 대화를 가리킵니다 보내는 사람 (포인터) 메시지를 보낸 사용자를 가리킵니다 내용 (문자열) 메시지의 텍스트 내용 읽기 (부울) 메시지가 읽혔는지 여부 생성일 (날짜) parse에 의해 자동으로 관리됨 타이핑 상태 클래스 대화 (포인터) 대화를 가리킴 사용자 (포인터) 입력 중인 사용자를 가리킴 입력 중 (부울) 사용자가 현재 입력 중인지 여부 메시징 인터페이스 구현 우리의 messagespage가 실시간 메시징을 어떻게 구현하는지 살펴보겠습니다 // 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 } 이 컴포넌트는 여러 상태를 유지합니다 conversations 사용자의 대화 목록 selectedconversation 현재 선택된 대화 messages 선택된 대화의 메시지 typingusers 현재 대화 중인 사용자 또한 livequery 구독을 저장하고 타이핑 표시기를 관리하기 위해 refs를 사용합니다 메시지를 위한 livequery 구독 실시간 메시징의 핵심은 현재 대화에서 메시지에 대한 livequery에 구독하는 것입니다 // 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); } }; 주요 livequery 메커니즘 쿼리 생성 현재 대화에서 메시지에 대한 쿼리를 생성합니다 쿼리 구독 우리는 query subscribe() 를 호출하여 변경 사항을 듣기 시작합니다 이벤트 처리 우리는 subscription on('create', callback) 를 사용하여 새 메시지를 처리합니다 구독 취소 우리는 구독 참조를 저장하고 필요할 때 구독을 취소합니다 라이브 쿼리로 타이핑 인디케이터 구현하기 타이핑 인디케이터는 라이브 쿼리로 구현된 또 다른 실시간 기능입니다 // 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); } }; 타이핑 상태 업데이트 사용자가 입력할 때, 우리는 그들의 타이핑 상태를 업데이트합니다 // 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); }; 메시지 전송 메시지를 전송할 때, 우리는 새로운 메시지 객체를 생성하고 livequery가 업데이트를 처리하도록 합니다 // 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, }); } }; 라이브 쿼리 구독 정리 더 이상 필요하지 않을 때 라이브 쿼리 구독을 정리하는 것이 중요합니다 // 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 라이브 쿼리 성능 고려사항 라이브 쿼리를 구현할 때 다음 성능 팁을 고려하세요 쿼리에 대해 구체적으로 작성하세요 필요한 데이터만 구독하세요 구독 범위를 제한하기 위해 제약 조건을 사용하세요 예를 들어, 현재 대화의 메시지만 구독하세요 구독을 신중하게 관리하세요 더 이상 필요하지 않은 데이터는 구독 해지하세요 상황이 변경될 때 새로운 구독을 생성하세요 나중에 구독 해지를 위해 구독 참조를 저장하세요 보안을 위해 acl을 사용하세요 메시지 및 대화 객체에 적절한 acl을 설정하세요 사용자가 자신이 참여한 대화만 접근할 수 있도록 하세요 livequery는 acl을 준수하므로, 권한이 없는 사용자는 업데이트를 받지 않습니다 livequery 서버 최적화 back4app 대시보드에서 livequery가 필요한 클래스를 구성하세요 실시간 업데이트가 필요하지 않은 클래스에 대해 livequery를 활성화하지 마세요 9단계 — 검색 기능 구현 이 단계에서는 소셜 네트워크를 위한 포괄적인 검색 기능을 구현할 것입니다 사용자는 다른 사용자, 콘텐츠에 의한 게시물 및 해시태그를 검색할 수 있습니다 이 기능은 사용자가 콘텐츠를 발견하고 플랫폼에서 다른 사람들과 연결하는 데 더 쉽게 만들어 줄 것입니다 back4app에서의 검색 이해하기 구현에 들어가기 전에 back4app에서 검색이 어떻게 작동하는지 이해해 봅시다 파스 쿼리 시스템 back4app은 parse server의 쿼리 시스템을 사용하여 검색합니다 쿼리는 여러 클래스에 걸쳐 수행될 수 있습니다 정확한 일치, 포함, 시작 등으로 검색할 수 있습니다 텍스트 검색 옵션 시작하기 특정 문자열로 시작하는 문자열을 찾습니다 포함 특정 하위 문자열을 포함하는 문자열을 찾습니다 일치 더 복잡한 패턴 매칭을 위해 정규 표현식을 사용합니다 전체 텍스트 (기업 기능) 고급 전체 텍스트 검색 기능을 제공합니다 성능 고려 사항 텍스트 검색은 리소스를 많이 소모할 수 있습니다 자주 검색되는 필드에 대한 인덱스를 생성해야 합니다 쿼리는 결과 수를 제한하도록 최적화되어야 합니다 검색 페이지 구축 우리의 searchpage 구성 요소는 다양한 유형의 검색을 처리하고 결과를 표시합니다 그 구조를 살펴보겠습니다 // 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 } 이 컴포넌트는 다음의 상태를 유지합니다 사용자가 입력한 검색 쿼리 수행 중인 검색 유형 검색 결과 로딩 상태 트렌딩 주제 사용자 검색 구현 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); } }; back4app의 주요 메커니즘 new parse query(parse user) 사용자 클래스에 대한 쿼리 생성 userquery matches('username', new regexp(query, 'i')) 사용자 이름에 대해 대소문자 구분 없는 정규 표현식 일치 수행 userquery limit(20) 성능 향상을 위해 결과 제한 userquery find() 쿼리를 실행하고 일치하는 사용자 반환 게시물 내용 검색 구현 이제 콘텐츠로 게시물을 검색하는 방법을 살펴보겠습니다 // 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); } }; back4app의 주요 메커니즘 parse object extend('post') post 클래스를 참조합니다 postquery matches('content', new regexp(query, 'i')) 게시물 콘텐츠에 대해 대소문자를 구분하지 않는 정규 표현식 일치를 수행합니다 postquery include('author') 단일 쿼리에서 저자 정보를 포함합니다 postquery descending('createdat') 생성일 기준으로 결과를 정렬합니다 해시태그 검색 구현 해시태그 검색은 다른 접근 방식을 요구합니다 해시태그가 포함된 게시물을 검색할 것입니다 // 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); } }; 주요 back4app 메커니즘 우리는 단어 경계가 있는 정규 표현식( \\\b )를 사용하여 실제 해시태그를 찾습니다 이 접근 방식은 콘텐츠에 특정 해시태그가 포함된 게시물을 찾습니다 트렌딩 주제 구현하기 트렌딩 주제를 구현하기 위해서는 최근 게시물을 분석하고 해시태그 발생 횟수를 세어야 합니다 // 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); } }; 주요 back4app 메커니즘 우리는 지난 7일 동안의 게시물을 쿼리합니다 query greaterthan('createdat', oneweekago) 우리는 콘텐츠를 분석하여 해시태그를 추출하고 계산합니다 우리는 빈도에 따라 정렬하여 가장 인기 있는 해시태그를 찾습니다 검색 실행 처리 이제 검색 유형에 따라 검색 실행을 처리하는 방법을 살펴보겠습니다 // 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); } }; back4app에서 검색 최적화 back4app에서 검색 성능을 최적화하려면 인덱스 생성 back4app 대시보드로 이동 "데이터베이스 브라우저"로 이동 > 클래스 선택 (예 사용자, 게시물) "인덱스" 탭 클릭 자주 검색되는 필드에 대한 인덱스 생성 사용자 클래스의 경우 username 게시물 클래스의 경우 content 쿼리 제약 조건 사용 항상 limit() 를 사용하여 결과 수 제한 사용 select() 를 사용하여 필요한 필드만 가져오기 사용 skip() 를 사용하여 대량 결과 집합의 페이지 매김 복잡한 검색을 위한 클라우드 함수 고려 더 복잡한 검색 로직의 경우, 클라우드 함수를 구현 이것은 서버 측 처리를 수행하고 최적화된 결과를 반환할 수 있게 해줍니다 고급 검색을 위한 예제 클라우드 함수 // 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; }); 10단계 — 소셜 네트워크 테스트 및 배포 이 마지막 단계에서는 애플리케이션을 테스트하는 방법, 프로덕션을 위한 준비, 호스팅 서비스에 배포하는 방법, 그리고 back4app 백엔드를 모니터링하고 확장하는 방법을 다룰 것입니다 이러한 단계는 소셜 네트워크가 프로덕션 환경에서 원활하게 운영되도록 보장하는 데 중요합니다 애플리케이션 테스트 배포하기 전에 애플리케이션을 철저히 테스트하여 버그나 문제를 잡는 것이 중요합니다 1 수동 테스트 애플리케이션의 모든 주요 기능을 포함하는 테스트 계획을 작성하십시오 사용자 인증 유효한 및 유효하지 않은 입력으로 테스트 등록 올바른 및 잘못된 자격 증명으로 로그인 테스트 비밀번호 재설정 기능 테스트 테스트 세션 지속성 및 로그아웃 게시 기능 텍스트와 이미지를 사용하여 게시물 만들기 테스트 피드에서 게시물 보기 테스트 게시물에 대한 좋아요 및 댓글 테스트 게시물 삭제 테스트 사회적 상호작용 사용자 프로필 보기 테스트 게시물에 댓글 달기 테스트 실시간 메시징 테스트 검색 기능 사용자 검색 테스트 게시물 검색 테스트 해시태그 검색 테스트 크로스 브라우저 테스트 chrome, firefox, safari 및 edge에서 테스트 모바일 브라우저에서 테스트 2 자동화 테스트 더 강력한 테스트를 위해 자동화된 테스트를 구현하세요 // 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 테스트 back4app 구성을 테스트하세요 클라우드 함수 다양한 입력으로 모든 클라우드 함수를 테스트하세요 보안 클래스 수준의 권한이 올바르게 작동하는지 확인하세요 라이브 쿼리 여러 클라이언트와 함께 실시간 기능을 테스트하세요 생산 준비하기 배포하기 전에 애플리케이션을 생산에 맞게 최적화하세요 1 환경 구성 개발 및 생산을 위한 별도의 환경 파일을 만드세요 # 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 빌드 최적화 react 빌드를 최적화하세요 // in your package json "scripts" { "analyze" "source map explorer 'build/static/js/ js'", "build" "generate sourcemap=false react scripts build" } 번들 크기를 분석하기 위해 source map explorer를 설치하세요 npm install save dev source map explorer 3 성능 최적화 초기 로드 시간을 줄이기 위해 코드 분할을 구현합니다 // 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> ); } 호스팅 서비스에 배포하기 react 애플리케이션을 배포할 수 있는 여러 옵션이 있습니다 1 vercel에 배포하기 vercel은 react 애플리케이션에 훌륭한 선택입니다 vercel cli 설치하기 npm install g vercel 애플리케이션 배포하기 vercel 프로덕션 배포를 위해 vercel prod 2 netlify에 배포하기 netlify는 또 다른 훌륭한 옵션입니다 netlify cli 설치하기 npm install g netlify cli 애플리케이션 빌드하기 npm run build netlify에 배포하기 netlify deploy 프로덕션 배포를 위해 netlify deploy prod 3 github pages에 배포하기 간단한 배포 옵션 gh pages 설치 npm install save dev gh pages package json에 추가 "homepage" "https //yourusername github io/your repo name", "scripts" { "predeploy" "npm run build", "deploy" "gh pages d build" } 배포 npm run deploy back4app 백엔드 모니터링 및 확장 소셜 네트워크가 성장함에 따라 back4app 백엔드를 모니터링하고 확장해야 합니다 1 성능 모니터링 back4app은 애플리케이션을 모니터링하기 위한 여러 도구를 제공합니다 대시보드 분석 api 요청, 저장소 사용량 및 파일 작업 모니터링 로그 오류 및 성능 문제에 대한 서버 로그 확인 성능 지표 응답 시간 추적 및 병목 현상 식별 이 도구에 접근하려면 back4app 대시보드로 이동 사용 통계를 위해 "분석"으로 이동 상세 작업 로그를 위해 "로그" 확인 2 백엔드 확장하기 사용자 기반이 성장하면 back4app 백엔드를 확장해야 할 수 있습니다 요금제 업그레이드 더 많은 리소스가 있는 상위 요금제로 이동 쿼리 최적화 성능 향상을 위해 인덱스를 사용하고 쿼리를 제한 캐싱 구현 자주 접근하는 데이터에 대해 클라이언트 측 캐싱 사용 3 데이터베이스 최적화 더 나은 성능을 위한 데이터베이스 최적화 인덱스 생성 자주 쿼리되는 필드에 인덱스를 추가합니다 // 예시 user 클래스의 'username' 필드에 인덱스 생성 const schema = new parse schema(' user'); schema addindex('username index', { username 1 }); schema update(); 집계 파이프라인 사용 복잡한 데이터 작업을 위해 // 예시 사용자별 게시물 수 세기 const pipeline = \[ { group { objectid '$author', count { $sum 1 } } } ]; const results = await parse cloud aggregate('post', pipeline); 4 미디어를 위한 cdn 구현 더 빠른 이미지 및 미디어 전송을 위해 cloudflare 또는 amazon cloudfront와 같은 cdn 구성 cdn을 사용하도록 back4app 파일 저장소 설정 업데이트 애플리케이션의 파일 url을 cdn 도메인을 사용하도록 업데이트 5 모니터링 알림 설정 문제가 발생했을 때 알림을 설정하세요 back4app 대시보드로 이동하세요 "앱 설정" > "알림"으로 이동하세요 다음에 대한 알림을 구성하세요 높은 api 사용량 오류율 급증 데이터베이스 크기 제한 서버 다운타임 달성한 내용 요약 이 튜토리얼을 통해 다음을 수행했습니다 강력한 백엔드 인프라 구축 back4app 계정을 만들고 애플리케이션을 구성했습니다 사용자, 게시물, 댓글 및 메시지를 위한 데이터베이스 스키마를 설계했습니다 구성된 보안 설정 및 클래스 수준 권한 실시간 기능을 위한 livequery 설정 현대적인 react 프론트엔드 구축 chakra ui 컴포넌트를 사용하여 반응형 ui를 만들었습니다 리액트 라우터를 사용하여 클라이언트 측 라우팅 구현 게시물, 댓글 및 사용자 프로필을 위한 재사용 가능한 구성 요소를 개발했습니다 parse javascript sdk를 사용하여 프론트엔드를 back4app에 연결했습니다 핵심 소셜 네트워크 기능 구현 사용자 인증 (가입, 로그인, 비밀번호 재설정) 게시물 생성 및 상호작용 (좋아요, 댓글) 사용자 프로필 및 설정 사용자 간의 실시간 메시징 사용자, 게시물 및 해시태그에 대한 검색 기능 생산을 위해 최적화됨 코드 분할과 같은 성능 최적화를 구현했습니다 개발 및 생산을 위한 환경 구성 설정 애플리케이션을 호스팅 서비스에 배포하는 방법을 배웠습니다 back4app 백엔드에 대한 모니터링 및 확장 전략을 탐색했습니다 이제 귀하의 특정 요구 사항을 충족하도록 확장하고 사용자 정의할 수 있는 소셜 네트워크 애플리케이션에 대한 확고한 기반이 마련되었습니다 애플리케이션 확장을 위한 다음 단계 소셜 네트워크 애플리케이션을 향상시키는 몇 가지 흥미로운 방법은 다음과 같습니다 고급 미디어 기능 비디오 업로드 및 재생 지원 추가 이미지 필터 및 편집 도구 구현 이야기 또는 일시적인 콘텐츠 기능 만들기 gif 및 기타 리치 미디어 지원 추가 향상된 사회적 상호작용 친구 추천을 위한 추천 엔진 구현 그룹 또는 커뮤니티 기능 추가 rsvp 기능이 있는 이벤트 만들기 모든 사용자 상호작용에 대한 알림 시스템 개발 수익화 옵션 프리미엄 멤버십 기능 구현 디지털 상품에 대한 인앱 구매 추가 사용자 간 거래를 위한 마켓플레이스 만들기 stripe와 같은 결제 처리기와 통합하기 모바일 경험 응용 프로그램을 프로그레시브 웹 앱(pwa)으로 변환하세요 react native를 사용하여 네이티브 모바일 앱 개발 모바일 장치에 대한 푸시 알림 구현 다양한 화면 크기와 방향에 맞게 ui 최적화 분석 및 통찰 사용자 참여를 추적하기 위한 분석 도구 통합 콘텐츠 성과를 위한 대시보드 만들기 새로운 기능에 대한 a/b 테스트 구현 사용자 행동 통찰력을 개발하여 플랫폼을 개선합니다 콘텐츠 조정 자동화된 콘텐츠 필터링 구현 부적절한 콘텐츠에 대한 보고 시스템 만들기 콘텐츠 조정을 위한 관리 도구 개발 지능형 콘텐츠 분석을 위한 기계 학습 사용 학습을 위한 추가 자료 지식과 기술을 계속 확장하기 위해, 다음은 몇 가지 유용한 자원입니다 back4app 문서 및 튜토리얼 back4app 문서 https //www back4app com/docs/get started/welcome 파스 자바스크립트 가이드 https //docs parseplatform org/js/guide/ back4app 유튜브 채널 https //www youtube com/c/back4app 리액트와 현대 자바스크립트 리액트 문서 https //reactjs org/docs/getting started html javascript info https //javascript info/ egghead io 리액트 강좌 https //egghead io/q/react ui 및 ux 디자인 chakra ui 문서 https //chakra ui com/docs/getting started ui 디자인 패턴 https //ui patterns com/ 닐슨 노먼 그룹 ux 연구 https //www nngroup com/articles/ 성능 최적화 web dev 성능 https //web dev/performance scoring/ 리액트 성능 최적화 https //reactjs org/docs/optimizing performance html 구글 페이지스피드 인사이트 https //developers google com/speed/pagespeed/insights/ 커뮤니티 및 지원 스택 오버플로우 https //stackoverflow\ com/questions/tagged/parse platform 파스 커뮤니티 포럼 https //community parseplatform org/ 리액트 개발자 커뮤니티 https //dev to/t/react 성공적인 소셜 네트워크를 구축하는 것은 반복적인 과정임을 기억하세요 탄탄한 기반(현재 가지고 있는 것)에서 시작하고, 사용자 피드백을 수집하며, 실제 사용 패턴에 따라 애플리케이션을 지속적으로 개선하세요 이 튜토리얼이 여러분에게 react와 back4app을 사용하여 놀라운 애플리케이션을 구축할 수 있는 지식과 자신감을 제공했기를 바랍니다 행복한 코딩 되세요