Project Templates
Social Network
How to Create a Social Media Platform: Step-by-Step Guide (2025)
68 분
소개 이 튜토리얼에서는 back4app을 백엔드 서비스로 사용하여 instagram과 같은 소셜 네트워크 플랫폼을 구축하는 방법을 배웁니다 back4app은 사용자 인증, 데이터 저장, 파일 업로드 및 복잡한 서버 인프라 없이 실시간 기능을 단순화하는 관리형 parse server 백엔드를 제공합니다 이 튜토리얼을 완료하면 다음과 같은 완전한 소셜 네트워크를 구축하게 됩니다 사용자 인증 (가입, 로그인, 비밀번호 재설정) 프로필 관리 이미지 업로드가 포함된 게시물 생성 소셜 상호작용 (좋아요, 댓글) 타이핑 표시가 있는 실시간 메시징 콘텐츠 검색 기능 소셜 네트워크 미리보기채팅 인터페이스사용자 프로필피드 페이지 언제든지 github 에서 전체 코드를 확인할 수 있습니다 전제 조건 이 튜토리얼을 완료하려면 다음이 필요합니다 back4app 계정 무료 계정에 가입하려면 back4app com https //www back4app com/ 로컬 머신에 node js 및 npm 설치 node js (버전 14 x 이상) 및 npm을 nodejs org https //nodejs org/ javascript 및 react에 대한 기본 이해 코드 편집기 visual studio code 또는 sublime text와 같은 최신 코드 편집기 1단계 — back4app 백엔드 설정 먼저, 새로운 back4app 프로젝트를 만들고 소셜 네트워크를 위한 데이터베이스 스키마를 설정합시다 새로운 back4app 프로젝트 만들기 back4app 계정에 로그인하고 대시보드로 이동합니다 "새 앱 만들기"를 클릭합니다 앱 이름으로 "back4gram"을 입력하고, 가장 가까운 서버 지역을 선택한 후 "생성"을 클릭합니다 데이터베이스 스키마 이해하기 우리의 소셜 네트워크는 back4app에서 다음 클래스를 필요로 합니다 사용자 (기본적으로 parse에 이미 존재함) 추가 필드인 약력 및 아바타로 확장될 것입니다 게시물 텍스트 콘텐츠와 이미지를 포함한 사용자 게시물을 저장합니다 댓글 게시물에 대한 댓글을 저장합니다 대화 사용자 간의 채팅 대화를 나타냅니다 메시지 대화 내의 개별 메시지입니다 타이핑 상태 사용자가 대화 중에 타이핑할 때를 추적합니다 데이터베이스 클래스 만들기 back4app 데이터베이스에 이러한 클래스를 생성해 봅시다 back4app 대시보드에서 "데이터베이스" 섹션으로 이동하세요 사용자 클래스 확장 이미 존재하는 "사용자" 클래스를 클릭하세요 다음 열을 추가하십시오 바이오 (유형 문자열) 아바타 (유형 파일) 팔로워 (유형 숫자, 기본값 0) 다음 (유형 숫자, 기본값 0) 게시물 클래스 만들기 "수업 만들기"를 클릭하세요 "post"를 클래스 이름으로 입력하고 "빈 클래스 만들기"를 선택하세요 다음 열을 추가하세요 내용 (유형 문자열) 저자 (유형 user에 대한 포인터) 이미지 (유형 파일) 좋아요 (유형 숫자, 기본값 0) 좋아요 (유형 배열) 생성일 (유형 날짜, 자동으로 추가됨) 댓글 클래스 만들기 "comment"라는 이름의 새 클래스를 만들고 다음 열을 추가하세요 내용 (유형 문자열) 저자 (유형 user에 대한 포인터) 게시물 (유형 게시물에 대한 포인터) 생성일 (유형 날짜, 자동으로 추가됨) 대화 클래스 만들기 "대화"라는 이름의 새 클래스를 만들고 다음 열을 추가하세요 참가자 (유형 배열) 마지막 메시지 (유형 문자열) 업데이트됨 (유형 날짜, 자동으로 추가됨) 메시지 클래스 생성 "메시지"라는 이름의 새 클래스를 만들고 다음 열을 추가하세요 텍스트 (유형 문자열) 발신자 (유형 user에 대한 포인터) 대화 (유형 대화에 대한 포인터) 생성일 (유형 날짜, 자동으로 추가됨) typingstatus 클래스를 생성하는 중 "typingstatus"라는 새 클래스를 만들고 다음 열을 추가하세요 사용자 (유형 user에 대한 포인터) 대화 (유형 대화에 대한 포인터) 입력 중 (유형 불리언) 클래스 권한 설정 애플리케이션 데이터를 보호하기 위해 각 클래스에 대해 적절한 접근 제어 목록(acl)을 구성하십시오 back4app 대시보드에서 "보안 및 키" 섹션으로 이동합니다 "클래스 수준 보안"에서 각 클래스에 대한 권한을 구성합니다 예를 들어, 게시물 클래스 공개 읽기 접근 활성화됨 (모두가 게시물을 볼 수 있음) 공개 쓰기 접근 활성화됨 (인증된 사용자가 게시물을 생성할 수 있음) 업데이트/삭제에 대한 clp를 추가하여 작성자만 제한합니다 실시간 기능을 위한 livequery 설정 메시징 및 입력 표시기와 같은 실시간 기능을 활성화하려면 back4app 대시보드에서 "서버 설정"으로 이동합니다 "parse server" 아래에서 "livequery"를 찾아 활성화합니다 livequery에서 모니터링할 클래스를 추가합니다 메시지 타이핑 상태 게시물 (좋아요 및 댓글에 대한 실시간 업데이트용) 애플리케이션 키 가져오기 프론트엔드에 연결하려면 back4app 애플리케이션 키가 필요합니다 "앱 설정" > "보안 및 키"로 이동합니다 다음 키를 기록해 두세요 애플리케이션 id javascript 키 서버 url livequery 서버 url 보안 키 2단계 — 프론트엔드를 back4app에 연결하기 프론트엔드와 back4app 백엔드 간의 연결을 설정해 보겠습니다 환경 변수 만들기 프로젝트의 루트에 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 자리 표시자 값을 실제 back4app 자격 증명으로 바꾸십시오 back4app으로 parse sdk 구성하기 back4app 자격 증명으로 parse를 초기화하기 위한 구성 파일을 만드십시오 // 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; 그런 다음 이 구성을 앱의 진입점에 가져오십시오 // src/index js import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import ' /utils/parseconfig'; // import parse configuration const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); 3단계 — back4app으로 인증 구현하기 back4app의 parse 서버는 parse user 클래스를 통해 포괄적인 사용자 관리 시스템을 제공합니다 parse 사용자 인증 이해하기 parse user 클래스는 사용자 관리를 위해 특별히 설계되었습니다 사용자 자격 증명(사용자 이름, 이메일, 비밀번호)을 저장합니다 인증 상태를 관리합니다 세션 토큰을 자동으로 처리합니다 사용자 등록 구현하기 parse로 사용자 등록을 구현하는 방법은 다음과 같습니다 // signup function const handlesignup = async () => { try { // create a new user const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); // additional user data user set('bio', ''); user set('followers', 0); user set('following', 0); // sign up the user await user signup(); // success user is automatically logged in console log('user registered successfully'); // navigate to feed or home page navigate('/feed'); } catch (error) { // handle specific parse errors console error('error signing up ', error message); if (error code === 202) { seterrors({ errors, username 'username already taken'}); } else if (error code === 203) { seterrors({ errors, email 'email already in use'}); } } }; 사용자 로그인 구현 parse를 사용하여 사용자 로그인을 구현하는 방법은 다음과 같습니다 // login function const handlelogin = async () => { try { // log in the user const user = await parse user login(username, password); // success user is logged in console log('user logged in successfully ', user getusername()); // navigate to feed or home page navigate('/feed'); } catch (error) { // handle login errors console error('error logging in ', error message); setloginerror(error message); } }; 현재 사용자 확인 parse는 세션 토큰을 자동으로 저장하여 사용자가 이미 로그인했는지 확인할 수 있습니다 // check if user is already logged in const checkcurrentuser = async () => { try { const currentuser = await parse user current(); if (currentuser) { console log('current user ', currentuser getusername()); return currentuser; } return null; } catch (error) { console error('error checking current user ', error); return null; } }; 비밀번호 재설정 구현 back4app은 내장된 비밀번호 재설정 흐름을 제공합니다 // password reset function const handlepasswordreset = async () => { try { await parse user requestpasswordreset(email); // success email sent to user console log('password reset email sent'); setresetemailsent(true); } catch (error) { // handle reset errors console error('error requesting password reset ', error message); setreseterror(error message); } }; 4단계 — 게시물 생성 및 표시 이제 back4app을 사용하여 게시물 생성 및 검색을 구현해 보겠습니다 게시물 생성 parse를 사용하여 새 게시물을 만드는 방법은 다음과 같습니다 // create post function const createpost = async () => { if (!postcontent trim() && !postimage) { console error('post must have content or image'); return; } 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(); console log('post created successfully'); return newpost; } catch (error) { console error('error creating post ', error message); throw error; } }; back4app의 주요 메커니즘 parse object extend('post') back4app의 post 클래스를 참조합니다 new post() post 클래스의 새 인스턴스를 생성합니다 parsefile save() 파일을 back4app의 저장소에 업로드합니다 newpost save() 게시물 객체를 back4app에 저장합니다 게시물 가져오기 back4app에서 게시물을 가져오는 방법은 다음과 같습니다 // fetch posts function const fetchposts = async (page = 0, limit = 10) => { try { // 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(limit); query skip(page limit); // execute the query const results = await query find(); // process the results const posts = 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 } })); return posts; } catch (error) { console error('error fetching posts ', error); throw error; } }; back4app의 주요 메커니즘 new parse query(post) post 클래스에 대한 쿼리를 생성합니다 query include('author') 관련 객체를 가져오기 위해 조인과 유사한 작업을 수행합니다 query descending('createdat') 생성 날짜에 따라 결과를 정렬합니다 query limit() 및 query skip() 페이지 매김을 구현합니다 post get('image') url() parse 파일 객체의 url을 가져옵니다 좋아요 기능 구현하기 게시물 좋아요를 구현하는 방법은 다음과 같습니다 // like/unlike post function const togglelike = async (postid) => { try { const currentuser = parse user current(); const userid = currentuser id; // get the post object const post = parse object extend('post'); const query = new parse query(post); const post = await query get(postid); // get current likes and likedby array const likedby = post get('likedby') || \[]; const isliked = likedby includes(userid); // update likes count and likedby array if (isliked) { // unlike remove user from likedby and decrement likes post set('likedby', likedby filter(id => id !== userid)); post set('likes', math max((post get('likes') || 1) 1, 0)); } else { // like add user to likedby and increment likes post set('likedby', \[ likedby, userid]); post set('likes', (post get('likes') || 0) + 1); } // save the updated post await post save(); return !isliked; // return new like status } catch (error) { console error('error toggling like ', error); throw error; } }; 5단계 — 게시물에 댓글 구현하기 back4app을 사용하여 댓글 기능을 구현해 보겠습니다 댓글 만들기 게시물에 댓글을 추가하는 방법은 다음과 같습니다 // add comment function const addcomment = async (postid, commentcontent) => { if (!commentcontent trim()) { console error('comment cannot be empty'); return; } try { // get the post object const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', commentcontent); comment set('author', parse user current()); comment set('post', post); // save the comment await comment save(); console log('comment added successfully'); return comment; } catch (error) { console error('error adding comment ', error); throw error; } }; 게시물의 댓글 가져오기 특정 게시물의 댓글을 가져오는 방법은 다음과 같습니다 // fetch comments function const fetchcomments = async (postid) => { try { // get the post object const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); // create a query for comments const comment = parse object extend('comment'); const query = new parse query(comment); // find comments for this post query equalto('post', post); // include the author information query include('author'); // sort by creation date query ascending('createdat'); // execute the query const results = await query find(); // process the results const comments = 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 } })); return comments; } catch (error) { console error('error fetching comments ', error); throw error; } }; 6단계 — 사용자 프로필 구현 이제 back4app을 사용하여 사용자 프로필을 구현해 보겠습니다 사용자 데이터 가져오기 사용자 프로필 데이터를 가져오는 방법은 다음과 같습니다 // fetch user profile function const fetchuserprofile = async (userid) => { try { // create a query for the user class const query = new parse query(parse user); // get the user by id const user = await query get(userid); // get user posts const post = parse object extend('post'); const postsquery = new parse query(post); postsquery equalto('author', user); postsquery include('author'); postsquery descending('createdat'); const posts = await postsquery find(); // process user data const userdata = { id user id, username user get('username'), bio user get('bio') || '', avatar user get('avatar') ? user get('avatar') url() null, followers user get('followers') || 0, following user get('following') || 0, posts 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') })) }; return userdata; } catch (error) { console error('error fetching user profile ', error); throw error; } }; 사용자 프로필 업데이트 사용자 프로필을 업데이트하는 방법은 다음과 같습니다 // update user profile function const updateuserprofile = async (profiledata) => { try { const currentuser = await parse user current(); // update profile fields if (profiledata bio !== undefined) { currentuser set('bio', profiledata bio); } if (profiledata avatarfile) { const parsefile = new parse file('avatar jpg', profiledata avatarfile); await parsefile save(); currentuser set('avatar', parsefile); } // save the updated user await currentuser save(); console log('profile updated successfully'); return currentuser; } catch (error) { console error('error updating profile ', error); throw error; } }; 7단계 — livequery를 사용한 실시간 메시징 구현 이제 back4app의 livequery 기능을 사용하여 실시간 메시징을 구현해 보겠습니다 대화 생성 새로운 대화를 생성하는 방법은 다음과 같습니다 // create conversation function const createconversation = async (participantids) => { try { // ensure current user is included in participants const currentuser = await parse user current(); const allparticipantids = \[ new set(\[currentuser id, participantids])]; // check if conversation already exists const existingconversation = await findexistingconversation(allparticipantids); if (existingconversation) { return existingconversation; } // get user objects for all participants const participantpointers = await promise all( allparticipantids map(async (id) => { const userquery = new parse query(parse user); return await userquery get(id); }) ); // create new conversation const conversation = parse object extend('conversation'); const conversation = new conversation(); conversation set('participants', participantpointers); conversation set('lastmessage', ''); await conversation save(); console log('conversation created successfully'); return conversation; } catch (error) { console error('error creating conversation ', error); throw error; } }; // helper to find existing conversation const findexistingconversation = async (participantids) => { try { const conversation = parse object extend('conversation'); const query = new parse query(conversation); // this is a simplified approach in production you'd need more complex query const results = await query find(); for (const conversation of results) { const participants = conversation get('participants') || \[]; const participantids = participants map(p => p id); // check if arrays have same elements (regardless of order) if (participantids length === participantids length && participantids every(id => participantids includes(id))) { return conversation; } } return null; } catch (error) { console error('error finding existing conversation ', error); return null; } }; 메시지 보내기 대화에서 메시지를 보내는 방법은 다음과 같습니다 // send message function const sendmessage = async (conversationid, messagetext) => { try { const currentuser = parse user current(); // get the conversation const conversation = parse object extend('conversation'); const conversationquery = new parse query(conversation); const conversation = await conversationquery get(conversationid); // create a new message const message = parse object extend('message'); const message = new message(); message set('text', messagetext); message set('sender', currentuser); message set('conversation', conversation); message set('read', false); await message save(); // update conversation with last message conversation set('lastmessage', messagetext); await conversation save(); console log('message sent successfully'); return message; } catch (error) { console error('error sending message ', error); throw error; } }; 메시지를 위한 라이브 쿼리 설정 실시간 메시지 업데이트를 구독하는 방법은 다음과 같습니다 // subscribe to messages function const subscribetomessages = async (conversationid, onnewmessage) => { try { // create a query for messages in this conversation const message = parse object extend('message'); const query = new parse query(message); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set up query constraints query equalto('conversation', conversationpointer); query include('sender'); // subscribe to the query const subscription = await query subscribe(); // listen for create events (new messages) subscription on('create', (message) => { // process the message const newmessage = { id message id, text message get('text'), 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') }; // call the callback with the new message onnewmessage(newmessage); // mark the message as read if from another user if (message get('sender') id !== parse user current() id) { markmessageasread(message); } }); return subscription; } catch (error) { console error('error subscribing to messages ', error); throw error; } }; // helper to mark message as read const markmessageasread = async (message) => { try { message set('read', true); await message save(); } catch (error) { console error('error marking message as read ', error); } }; 타이핑 표시 구현하기 livequery로 타이핑 표시를 구현하는 방법은 다음과 같습니다 // update typing status function const updatetypingstatus = async (conversationid, istyping) => { try { const currentuser = parse user current(); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // check if typing status 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 status typingstatus set('istyping', istyping); } else { // create new 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); } }; // subscribe to typing status function const subscribetotypingstatus = async (conversationid, ontypingstatuschange) => { try { // create a query for typing status in this conversation const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set up query constraints query equalto('conversation', conversationpointer); query include('user'); // subscribe to the query const subscription = await query subscribe(); // listen for update events subscription on('update', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // call the callback with typing status ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); // also listen for create events subscription on('create', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // call the callback with typing status ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); return subscription; } catch (error) { console error('error subscribing to typing status ', error); throw error; } }; 8단계 — 검색 기능 구현하기 back4app의 쿼리 시스템을 사용하여 검색 기능을 구현해 보겠습니다 사용자 검색하기 사용자를 검색하는 방법은 다음과 같습니다 // search users function const searchusers = async (query, limit = 20) => { try { // create a query on the user class const userquery = new parse query(parse user); // search by username (case insensitive) userquery matches('username', new regexp(query, 'i')); // limit results to improve performance userquery limit(limit); // execute the query const users = await userquery find(); // process the results 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') || '' })); return userresults; } catch (error) { console error('error searching users ', error); throw error; } }; \### searching for posts here's how to search for posts by content ```javascript // search posts function const searchposts = async (query, limit = 20) => { 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(limit); // execute the query const posts = await postquery find(); // process the results 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 } })); return postresults; } catch (error) { console error('error searching posts ', error); throw error; } }; 해시태그 검색하기 특정 해시태그로 게시물을 검색하는 방법은 다음과 같습니다 // search hashtags function const searchhashtags = async (tag, limit = 20) => { try { // remove # if present at the beginning const hashtagquery = tag startswith('#') ? tag substring(1) tag; // 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 // using 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(limit); // execute the query const posts = await postquery find(); // process the results 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 } })); return hashtagresults; } catch (error) { console error('error searching hashtags ', error); throw error; } }; 9단계 — 고급 back4app 기능 구현 소셜 네트워크 애플리케이션을 향상시킬 수 있는 몇 가지 고급 back4app 기능을 살펴보겠습니다 클라우드 함수 back4app은 클라우드 함수를 사용하여 서버 측 로직을 구현할 수 있도록 합니다 이는 서버에서 실행되고 클라이언트 앱에서 호출할 수 있는 javascript 함수입니다 댓글 알림을 추적하기 위한 클라우드 함수의 예는 다음과 같습니다 // in your back4app cloud code section 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 대시보드로 이동 "클라우드 코드" > "클라우드 함수"로 이동 위의 코드로 새 함수를 생성 함수를 배포 고급 검색을 위한 클라우드 코드 더 복잡한 검색 기능을 위해 cloud function을 생성할 수 있습니다 // in your back4app cloud code section 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; default throw new error("invalid search type"); } return results; }); 클라이언트에서 이 함수를 호출하려면 // call the advanced search cloud function const calladvancedsearch = async (query, type, limit = 20) => { try { const results = await parse cloud run('advancedsearch', { query, type, limit }); return results; } catch (error) { console error('error calling advanced search ', error); throw error; } }; 백그라운드 작업 구현 트렌드 주제 계산과 같은 반복 작업을 위해 백그라운드 작업을 사용할 수 있습니다 // in your back4app cloud code section parse cloud job("calculatetrendingtopics", async () => { // get posts from the last 7 days const post = parse object extend("post"); const query = new parse query(post); const oneweekago = new date(); oneweekago setdate(oneweekago getdate() 7); query greaterthan('createdat', oneweekago); // limit to a reasonable number for analysis query limit(1000); const posts = await query find({ usemasterkey true }); // extract hashtags and count occurrences const hashtagcounts = {}; posts foreach(post => { const content = post get('content') || ''; const hashtags = content match(/#(\w+)/g) || \[]; 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 // save to a trendingtopics class const trendingtopics = parse object extend("trendingtopics"); const trending = new trendingtopics(); trending set("topics", trendingarray); trending set("calculatedat", new date()); await trending save(null, { usemasterkey true }); return "trending topics calculated successfully"; }); 이 작업을 설정하려면 back4app 대시보드로 이동 "cloud code" > "background jobs"로 이동 위의 코드로 새 작업을 생성 매일 또는 매주 실행되도록 예약 parse server hooks 사용하기 back4app은 특정 작업 전후에 자동으로 실행되는 서버 측 후크를 구현할 수 있도록 해줍니다 이러한 후크는 데이터 검증, 수정 또는 부작용을 유발하는 데 유용합니다 // beforesave hook to sanitize post content parse cloud beforesave("post", async (request) => { const post = request object; let content = post get("content"); // sanitize content (example remove profanity) const profanitylist = \["badword1", "badword2", "badword3"]; profanitylist foreach(word => { const regex = new regexp(word, "gi"); content = content replace(regex, " "); }); // update the content post set("content", content); }); // afterdelete hook to clean up related data parse cloud afterdelete("post", async (request) => { const post = request object; const postid = post id; // delete all comments associated with this post const comment = parse object extend("comment"); const query = new parse query(comment); query equalto("post", post); const comments = await query find({ usemasterkey true }); await parse object destroyall(comments, { usemasterkey true }); console log(`deleted ${comments length} comments for post ${postid}`); }); 푸시 알림 구현하기 back4app은 사용자 기기에 알림을 전송할 수 있는 강력한 푸시 알림 시스템을 제공합니다 푸시 알림을 구현하는 방법은 다음과 같습니다 먼저, back4app 대시보드에서 푸시 구성 설정을 합니다 "앱 설정" > "푸시"로 이동합니다 ios 및/또는 android 자격 증명을 구성합니다 사용자가 새 메시지를 받을 때 푸시 알림을 전송합니다 // in your cloud code parse cloud aftersave("message", async (request) => { const message = request object; const conversation = message get("conversation"); const sender = message get("sender"); // get the conversation to find recipients const conversationquery = new parse query("conversation"); const fullconversation = await conversationquery get(conversation id, { usemasterkey true }); // get all participants const participants = fullconversation get("participants") || \[]; // send notification to all participants except the sender for (const participant of participants) { if (participant id !== sender id) { // create a query for this user's installations const pushquery = new parse query(parse installation); pushquery equalto("user", participant); // send the push notification await parse push send({ where pushquery, data { alert `new message from ${sender get("username")}`, sound "default", badge "increment", sender sender id, messageid message id, conversationid conversation id } }, { usemasterkey true }); } } }); 역할 기반 접근 제어 사용하기 back4app은 보다 세분화된 수준에서 권한을 관리할 수 있는 역할 기반 접근 제어 시스템을 제공합니다 // create an admin role const createadminrole = async (adminuser) => { try { // create a new role const adminrole = new parse role("administrator", new parse acl()); // set initial users of this role adminrole getusers() add(adminuser); // save the role await adminrole save(null, { usemasterkey true }); console log("admin role created successfully"); return adminrole; } catch (error) { console error("error creating admin role ", error); throw error; } }; // set permissions for posts to be moderated by admins const setpostmoderatorpermissions = async () => { try { // get the admin role const rolequery = new parse query(parse role); rolequery equalto("name", "administrator"); const adminrole = await rolequery first({ usemasterkey true }); // set class level permissions const schema = new parse schema("post"); // add class level permissions for admin role await schema setclp({ get { " " true }, find { " " true }, create { " " true }, update { "role\ administrator" true, "" true }, // creator and admins can update delete { "role\ administrator" true, "" true }, // creator and admins can delete addfield { "role\ administrator" true } }); await schema update(); console log("admin permissions for post class set successfully"); } catch (error) { console error("error setting admin permissions ", error); throw error; } }; 웹훅 구현 back4app은 외부 서비스와 통합하기 위해 웹훅을 설정할 수 있도록 합니다 // example cloud function that calls an external webhook parse cloud aftersave("post", async (request) => { const post = request object; // only trigger for new posts if (request original) return; try { // get author information const author = post get("author"); await author fetch({ usemasterkey true }); // prepare data for webhook const webhookdata = { postid post id, content post get("content"), authorname author get("username"), createdat post get("createdat") }; // call external webhook (example notify a content moderation service) const response = await parse cloud httprequest({ method 'post', url 'https //your webhook url com/new post', headers { 'content type' 'application/json' }, body webhookdata }); console log("webhook notification sent successfully ", response data); } catch (error) { console error("error sending webhook notification ", error); } }); 10단계 — back4app 성능 최적화 소셜 네트워크가 성장함에 따라 back4app 백엔드를 성능과 확장성을 위해 최적화해야 합니다 이 섹션에서는 사용자 기반이 확장되더라도 애플리케이션이 빠르고 반응성이 유지되도록 보장하는 필수 전략을 다룹니다 데이터베이스 최적화 효율적인 데이터베이스 설계와 쿼리는 애플리케이션 성능에 매우 중요합니다 인덱스 생성 인덱스는 자주 검색되는 필드에서 쿼리 성능을 극적으로 향상시킵니다 back4app 대시보드로 이동 "데이터베이스 브라우저"로 이동 > 클래스 선택 (예 post) "인덱스" 탭을 클릭 자주 검색되는 필드에 대한 인덱스 생성 // example creating indexes programmatically const createpostindexes = async () => { try { const schema = new parse schema('post'); // add an index on the author field await schema addindex('author index', { author 1 }); // add an index on createdat for timeline queries await schema addindex('createdat index', { createdat 1 }); // add a compound index for author + createdat await schema addindex('author date index', { author 1, createdat 1 }); await schema update(); console log('indexes created successfully'); } catch (error) { console error('error creating indexes ', error); } }; 인덱스할 주요 필드 작성자 는 사용자별 쿼리를 더 빠르게 하기 위해 post 클래스에서 사용됩니다 createdat 는 타임라인 로딩을 더 빠르게 하기 위해 사용됩니다 사용자 이름 는 사용자 검색을 더 빠르게 하기 위해 user 클래스에서 사용됩니다 참여자 는 메시지 필터링을 더 빠르게 하기 위해 conversation 클래스에서 사용됩니다 쿼리 최적화 서버 부하와 응답 시간을 줄이기 위해 쿼리를 최적화하세요 // bad fetches all fields and doesn't use limit const fetchuserposts = async (userid) => { const query = new parse query('post'); query equalto('author', userid); const results = await query find(); return results; }; // good only fetches needed fields and uses limit const fetchuserposts = async (userid, page = 0, limit = 20) => { const post = parse object extend('post'); const query = new parse query(post); // get user pointer const userpointer = { type 'pointer', classname ' user', objectid userid }; // only get posts for this user query equalto('author', userpointer); // only select fields we need query select('content', 'image', 'likes', 'createdat'); // sort by date, newest first query descending('createdat'); // implement pagination query limit(limit); query skip(page limit); const results = await query find(); return results; }; 배치 작업 여러 객체를 포함하는 작업의 경우, api 호출을 줄이기 위해 배치 작업을 사용하세요 // update multiple posts in a single request const updatemultipleposts = async (postids, updatedata) => { try { const post = parse object extend('post'); const posts = postids map(id => { const post = new post(); post id = id; return post; }); // set the same update data on all posts posts foreach(post => { object entries(updatedata) foreach((\[key, value]) => { post set(key, value); }); }); // save all posts in a single request await parse object saveall(posts); console log('all posts updated successfully'); } catch (error) { console error('error updating posts ', error); throw error; } }; // delete multiple comments in a single request const deletemultiplecomments = async (commentids) => { try { const comment = parse object extend('comment'); const comments = commentids map(id => { const comment = new comment(); comment id = id; return comment; }); // delete all comments in a single request await parse object destroyall(comments); console log('all comments deleted successfully'); } catch (error) { console error('error deleting comments ', error); throw error; } }; 캐싱 구현 자주 접근하는 데이터에 대한 클라이언트 측 캐싱을 구현하여 api 호출을 줄입니다 // caching user profiles const userprofilecache = new map(); const cache expiry = 10 60 1000; // 10 minutes in milliseconds const getuserprofile = async (userid, forcerefresh = false) => { // check if we have a valid cache entry const now = date now(); const cacheentry = userprofilecache get(userid); if (!forcerefresh && cacheentry && (now cacheentry timestamp < cache expiry)) { console log('using cached user profile'); return cacheentry data; } try { // fetch from back4app const query = new parse query(parse user); const user = await query get(userid); // process user data const userdata = { id user id, username user get('username'), bio user get('bio') || '', avatar user get('avatar') ? user get('avatar') url() null, followers user get('followers') || 0, following user get('following') || 0 }; // store in cache with timestamp userprofilecache set(userid, { data userdata, timestamp now }); return userdata; } catch (error) { console error('error fetching user profile ', error); // return cached data even if expired in case of error if (cacheentry) { console log('using expired cache due to error'); return cacheentry data; } throw error; } }; // clear cache for a user when their profile is updated const invalidateusercache = (userid) => { userprofilecache delete(userid); }; livequery 효율적으로 사용하기 livequery는 실시간 기능에 강력하지만 성능 문제를 피하기 위해 효율적으로 사용해야 합니다 // efficient livequery usage const setupefficientlivequery = async (conversationid) => { try { // only subscribe to messages in the active conversation if (!conversationid) return null; // create a focused query const message = parse object extend('message'); const query = new parse query(message); // get the conversation pointer const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // only get messages for this conversation query equalto('conversation', conversationpointer); // only include necessary fields query select('text', 'sender', 'createdat'); // limit initial results query limit(50); query descending('createdat'); // create subscription const subscription = await query subscribe(); subscription on('create', (message) => { // handle new message console log('new message ', message get('text')); // update ui with new message }); // unsubscribe when switching conversations return subscription; } catch (error) { console error('error setting up livequery ', error); return null; } }; // always unsubscribe when done const cleanuplivequery = (subscription) => { if (subscription) { subscription unsubscribe(); } }; livequery에 대한 모범 사례 실시간 업데이트가 필요한 데이터만 구독하세요 요청하는 필드를 제한하세요 select() 더 이상 데이터가 필요하지 않을 때는 항상 구독을 취소하세요 제약 조건을 사용하여 구독 범위를 좁히세요 초기 데이터 로딩을 위해 livequery와 일반 쿼리를 결합하는 것을 고려하세요 파일 처리 최적화 효율적인 파일 처리는 이미지 업로드가 있는 소셜 네트워크에 매우 중요합니다 // resize images before upload to reduce storage and bandwidth const uploadresizedimage = async (originalfile, maxwidth = 1200, maxheight = 1200) => { return new promise((resolve, reject) => { try { const reader = new filereader(); reader onload = (event) => { const img = new image(); img onload = () => { // calculate new dimensions let width = img width; let height = img height; if (width > maxwidth) { height = math round(height (maxwidth / width)); width = maxwidth; } if (height > maxheight) { width = math round(width (maxheight / height)); height = maxheight; } // create canvas for resizing const canvas = document createelement('canvas'); canvas width = width; canvas height = height; // draw resized image const ctx = canvas getcontext('2d'); ctx drawimage(img, 0, 0, width, height); // convert to blob canvas toblob(async (blob) => { const resizedfile = new file(\[blob], originalfile name, { type originalfile type, lastmodified date now() }); // upload to back4app const parsefile = new parse file(resizedfile name, resizedfile); await parsefile save(); resolve(parsefile); }, originalfile type, 0 8); // 0 8 quality for jpeg }; img src = event target result; }; reader readasdataurl(originalfile); } catch (error) { reject(error); } }); }; // use thumbnails for preview images const createthumbnailversion = async (originalfile, thumbnailwidth = 200) => { // similar to above but creating a smaller thumbnail // // return both original and thumbnail return { original originalparsefile, thumbnail thumbnailparsefile }; }; back4app 앱 모니터링 back4app은 애플리케이션 성능을 모니터링하기 위한 여러 도구를 제공합니다 대시보드 분석 높은 수준의 사용 통계 제공 api 요청, 저장소 사용량 및 파일 작업 모니터링 사용 패턴 및 피크 시간 식별 로그 작업에 대한 자세한 정보 제공 서버 로그를 검토하여 오류 및 성능 문제 확인 문제 격리를 위해 로그를 유형별로 필터링 성능 메트릭 응답 시간 및 시스템 상태 추적 쿼리 성능 모니터링 느린 작업 식별 문제 발생 가능성에 대한 알림을 설정하세요 back4app 대시보드에서 "앱 설정" > "알림"으로 이동하세요 다음에 대한 알림을 설정하세요 높은 api 사용량 (요금제 한도에 근접) 오류율 급증 (애플리케이션 문제를 나타냄) 서버 다운타임 (모든 사용자에게 영향을 미침) 저장소 사용량 (용량에 근접) // implementing custom logging for performance monitoring const logperformance = async (operation, starttime) => { const duration = date now() starttime; // log to back4app const performancelog = parse object extend('performancelog'); const log = new performancelog(); log set('operation', operation); log set('duration', duration); log set('user', parse user current() ? parse user current() id 'anonymous'); log set('timestamp', new date()); await log save(null, { usemasterkey true }); // log locally too console log(`performance ${operation} took ${duration}ms`); }; // example usage const fetchtimelineposts = async () => { const starttime = date now(); try { // query code here // // calculate and log performance await logperformance('fetchtimelineposts', starttime); return results; } catch (error) { console error('error ', error); throw error; } }; 확장 전략 애플리케이션이 성장함에 따라 다음과 같은 확장 전략을 고려하세요 back4app 요금제 업그레이드 사용자 기반이 확장됨에 따라 더 높은 계층으로 이동 사용량을 모니터링하고 한계에 도달하기 전에 업그레이드 대규모 컬렉션에 대한 샤딩 구현 매우 큰 데이터 세트의 경우 데이터를 여러 클래스에 분할 예를 들어, 연도별 또는 월별로 메시지를 분할 // example of sharded messages implementation const sendshardedmessage = async (conversationid, text, sender) => { try { // determine which shard to use (e g , by current month) const now = new date(); const shardname = `message ${now\ getfullyear()} ${now\ getmonth() + 1}`; // create dynamic class reference const messageshard = parse object extend(shardname); const message = new messageshard(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; // set message data message set('conversation', conversationpointer); message set('text', text); message set('sender', sender); // save to appropriate shard await message save(); return message; } catch (error) { console error('error sending sharded message ', error); throw error; } }; // fetch messages across shards const fetchshardedmessages = async (conversationid, limit = 50) => { try { // get list of all message shards const shards = await getmessageshardnames(); // create a query for each shard const queries = shards map(shardname => { const messageshard = parse object extend(shardname); const query = new parse query(messageshard); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; query equalto('conversation', conversationpointer); return query; }); // combine queries const mainquery = parse query or( queries); mainquery descending('createdat'); mainquery limit(limit); return await mainquery find(); } catch (error) { console error('error fetching sharded messages ', error); throw error; } }; 캐싱 서버 구현 자주 접근하는 데이터에 대해 redis 또는 기타 캐싱 솔루션 사용 클라우드 코드 및 외부 서비스를 통해 구현 콘텐츠 전송 네트워크(cdn) 사용 더 빠른 파일 전송을 위해 cdn 구성 특히 이미지 및 미디어 파일에 중요 보안 최적화 보안과 성능은 밀접하게 관련되어 있습니다 이러한 최적화는 두 가지 모두를 개선합니다 포인터 권한 구현 포인터 권한을 사용하여 관련 객체에 대한 접근을 제한합니다 이것은 보안을 희생하지 않고 더 효율적인 쿼리를 가능하게 합니다 // efficient acl usage set default acl parse setdefaultacl(new parse acl({ publicreadaccess true }), true); // only use specific acls for private content const createprivatepost = async (content, image) => { const currentuser = parse user current(); // create a new private post const post = parse object extend('post'); const post = new post(); // set post data post set('content', content); post set('author', currentuser); if (image) { const parsefile = new parse file(image name, image); await parsefile save(); post set('image', parsefile); } // create private acl const privateacl = new parse acl(currentuser); // only the creator can read and write privateacl setreadaccess(currentuser id, true); privateacl setwriteaccess(currentuser id, true); // set the acl post setacl(privateacl); // save the post await post save(); return post; }; back4app 서버 최적화 back4app에서 parse server 구성을 최적화하세요 서버 매개변수 구성 "앱 설정" > "서버 설정"으로 이동 장기 실행 작업에 대한 타임아웃 설정 조정 최대 요청 한도 구성 요금 제한 설정 남용을 방지하고 공정한 사용을 보장하기 위해 요금 제한 구현 앞서 설명한 대로 클라우드 코드 통해 구성 이러한 성능 최적화 전략을 구현함으로써, 귀하의 소셜 네트워크 애플리케이션은 성장에 잘 대비하고 사용자 기반이 확장되더라도 우수한 성능을 유지할 수 있습니다 애플리케이션의 성능을 지속적으로 모니터링하고 필요에 따라 점진적인 개선을 하는 것을 잊지 마세요 결론 이 튜토리얼에서는 back4app을 백엔드 서비스로 사용하여 완전한 소셜 네트워크 애플리케이션을 구축하는 방법을 배웠습니다 다음과 같은 핵심 기능을 구현했습니다 사용자 인증 및 프로필 관리 게시물 생성 및 소셜 상호작용 livequery를 통한 실시간 메시징 검색 기능 cloud functions 및 background jobs와 같은 고급 기능 back4app의 parse server는 복잡한 서버 인프라에 대한 걱정 없이 훌륭한 사용자 경험을 구축하는 데 집중할 수 있도록 강력하고 확장 가능한 백엔드 솔루션을 제공합니다 다음 단계 소셜 네트워크 애플리케이션을 더욱 향상시키기 위해 다음을 고려하세요 푸시 알림 구현 back4app의 푸시 서비스를 사용하여 분석 추가 사용자 참여 추적을 위해 대용량 미디어 파일을 위한 파일 저장 옵션 설정 cloud functions를 사용한 콘텐츠 조정 구현 react native를 사용하여 동일한 back4app 백엔드로 모바일 앱 만들기 back4app의 기능을 활용하고 이 튜토리얼에 설명된 최적화 전략을 따르면 수천 명의 사용자를 처리하면서 높은 성능을 유지하는 소셜 네트워크를 구축할 수 있습니다 추가 리소스 back4app에 대한 지식을 계속 확장하기 위해, 다음은 몇 가지 유용한 리소스입니다 back4app 문서 back4app 문서 https //www back4app com/docs/get started/welcome 파스 자바스크립트 가이드 https //docs parseplatform org/js/guide/ back4app 유튜브 채널 https //www youtube com/c/back4app 파스 서버와 라이브 쿼리 parse server 문서 https //docs parseplatform org/parse server/guide/ 라이브 쿼리 가이드 https //docs parseplatform org/parse server/guide/#live queries 보안 및 최적화 클래스 수준 권한 가이드 https //docs parseplatform org/rest/guide/#class level permissions acl 보안 가이드 https //docs parseplatform org/js/guide/#security 성능 최적화 https //docs parseplatform org/js/guide/#performance 고급 기능 클라우드 코드 가이드 https //docs parseplatform org/cloudcode/guide/ 백그라운드 작업 https //docs parseplatform org/cloudcode/guide/#cloud jobs 푸시 알림 https //docs parseplatform org/js/guide/#push notifications 성공적인 소셜 네트워크를 구축하는 것은 반복적인 과정임을 기억하세요 탄탄한 기반(현재 가지고 있는 것)에서 시작하고, 사용자 피드백을 수집하며, 실제 사용 패턴에 따라 애플리케이션을 지속적으로 개선하세요 이 튜토리얼이 back4app으로 멋진 애플리케이션을 구축하는 데 필요한 지식과 자신감을 제공했기를 바랍니다 코딩을 즐기세요!