Project Templates
Social Network
How to Create a Social Media Platform: Step-by-Step Guide (2025)
70 мин
введение в этом учебном пособии вы узнаете, как создать платформу социальной сети, подобную instagram, используя back4app в качестве вашего серверного решения back4app предоставляет управляемый сервер parse, который упрощает аутентификацию пользователей, хранение данных, загрузку файлов и функции в реальном времени без необходимости в сложной серверной инфраструктуре завершив это учебное пособие, вы создадите полноценную социальную сеть с аутентификация пользователей (регистрация, вход, сброс пароля) управление профилем создание постов с загрузкой изображений социальные взаимодействия (лайки, комментарии) обмен сообщениями в реальном времени с индикаторами ввода функциональность поиска контента предварительный просмотр социальной сетиинтерфейс чатапрофиль пользователястраница ленты в любой момент вы можете получить полный код на 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 расширение класса user нажмите на уже существующий класс "пользователь" добавьте следующие столбцы био (тип строка) аватар (тип файл) подписчики (тип число, по умолчанию 0) следующий (тип число, по умолчанию 0) создание класса post нажмите "создать класс" введите "post" в качестве имени класса и выберите "создать пустой класс" добавьте следующие столбцы содержимое (тип строка) автор (тип указатель на пользователь) изображение (тип файл) лайки (тип число, по умолчанию 0) понравилось (тип массив) создано (тип дата, добавлено автоматически) создание класса comment создайте новый класс с именем "comment" с этими столбцами содержимое (тип строка) автор (тип указатель на пользователь) пост (тип указатель на пост) создано (тип дата, добавлено автоматически) создание класса conversation создайте новый класс под названием "conversation" с этими столбцами участники (тип массив) последнеесообщение (тип строка) обновлено (тип дата, добавляется автоматически) создание класса message создайте новый класс с именем "сообщение" с этими столбцами текст (тип строка) отправитель (тип указатель на пользователь) разговор (тип указатель на разговор) создано (тип дата, добавлено автоматически) создание класса typingstatus создайте новый класс под названием "typingstatus" с этими столбцами пользователь (тип указатель на пользователь) разговор (тип указатель на разговор) печатает (тип логическое) настройка разрешений класса чтобы защитить данные вашего приложения, настройте соответствующие списки управления доступом (acl) для каждого класса перейдите в раздел "безопасность и ключи" на вашей панели управления back4app в разделе "безопасность на уровне класса" настройте разрешения для каждого класса например, в классе post class общий доступ для чтения включен (все могут видеть посты) общий доступ для записи включен (авторизованные пользователи могут создавать посты) добавьте clp для обновления/удаления, чтобы ограничить доступ только для автора настройка livequery для функций в реальном времени чтобы включить функции в реальном времени, такие как обмен сообщениями и индикаторы ввода перейдите в "настройки сервера" на вашей панели управления back4app в разделе "parse server" найдите "livequery" и включите его добавьте эти классы для мониторинга с помощью livequery сообщение статус набора текста пост (для обновлений в реальном времени по лайкам и комментариям) получение ключей вашего приложения вам понадобятся ключи вашего приложения back4app для подключения вашего фронтенда перейдите в "настройки приложения" > "безопасность и ключи" запишите следующие ключи id приложения ключ javascript url сервера url сервера livequery ключи безопасности шаг 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 настройка parse sdk с back4app создайте файл конфигурации для инициализации parse с вашими учетными данными back4app // 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 сервер parse от back4app предоставляет комплексную систему управления пользователями через класс 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') ссылается на класс post в back4app 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() получает url объекта parse file реализация функции «нравится» вот как реализовать лайки к постам // 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 теперь давайте реализуем обмен сообщениями в реальном времени, используя функцию livequery от back4app создание разговоров вот как создать новый разговор // 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; } }; настройка livequery для сообщений вот как подписаться на обновления сообщений в реальном времени // 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 code" > "облачные функции" создайте новую функцию с приведенным выше кодом разверните функцию облачный код для расширенного поиска для более сложной функциональности поиска вы можете создать облачную функцию // 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" > "фоновые задачи" создайте новую задачу с приведенным выше кодом запланируйте ее выполнение ежедневно или еженедельно использование хуков parse server 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}`); }); реализация push уведомлений back4app предоставляет надежную систему push уведомлений, которая позволяет вам отправлять уведомления на устройства ваших пользователей вот как реализовать push уведомления сначала настройте push configuration в вашей панели управления back4app перейдите в "настройки приложения" > "push" настройте свои учетные данные для ios и/или android отправьте push уведомление, когда пользователь получает новое сообщение // 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 или другие решения для кэширования для часто запрашиваемых данных реализуйте через cloud code и внешние сервисы используйте сети доставки контента (cdn) настройте cdn для более быстрой доставки файлов особенно важно для изображений и медиафайлов оптимизация безопасности безопасность и производительность тесно связаны эти оптимизации улучшают оба аспекта реализовать разрешения указателей используйте разрешения указателей для ограничения доступа к связанным объектам это позволяет выполнять более эффективные запросы без ущерба для безопасности // set up pointer permissions const setuppointerpermissions = async () => { try { // get the message class schema const schema = new parse schema('message'); // add pointer permissions only conversation participants can access messages const pointerpermissions = { 'conversation' { 'read' { 'participantsfield' 'participants' } } }; // update the schema with pointer permissions await schema addpointerpermissions(pointerpermissions); await schema update(); console log('pointer permissions set successfully'); } catch (error) { console error('error setting pointer permissions ', error); } }; эффективно использовать acl parse acl обеспечивают безопасность, но могут повлиять на производительность при чрезмерном использовании рассмотрите возможность использования разрешений на уровне класса для общих случаев и acl для исключений // 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 оптимизируйте конфигурацию вашего parse server в back4app настройте параметры сервера перейдите в "настройки приложения" > "настройки сервера" настройте параметры таймаута для длительных операций настройте максимальные лимиты запросов настройте ограничение частоты реализуйте ограничение частоты, чтобы предотвратить злоупотребления и обеспечить справедливое использование настройте через cloud code, как показано ранее реализуя эти стратегии оптимизации производительности, ваше приложение социальной сети будет хорошо подготовлено к росту и поддержанию отличной производительности, даже по мере расширения вашей пользовательской базы не забывайте постоянно отслеживать производительность вашего приложения и вносить постепенные улучшения по мере необходимости заключение в этом руководстве вы узнали, как создать полное приложение социальной сети, используя back4app в качестве вашего серверного решения вы реализовали основные функции, такие как аутентификация пользователей и управление профилем создание постов и социальные взаимодействия мгновенные сообщения с помощью livequery функция поиска расширенные функции, такие как облачные функции и фоновые задачи parse server от back4app предоставляет мощное и масштабируемое серверное решение, которое позволяет вам сосредоточиться на создании отличного пользовательского опыта, не беспокоясь о сложной серверной инфраструктуре следующие шаги чтобы дополнительно улучшить ваше приложение социальной сети, рассмотрите возможность реализация push уведомлений с использованием push сервиса back4app добавление аналитики для отслеживания вовлеченности пользователей настройка параметров хранения файлов для больших медиафайлов реализация модерации контента с использованием облачных функций создание мобильного приложения с использованием react native с тем же сервером back4app используя функции back4app и следуя стратегиям оптимизации, изложенным в этом руководстве, вы можете создать социальную сеть, которая масштабируется для обработки тысяч пользователей, сохраняя при этом высокую производительность дополнительные ресурсы чтобы продолжить расширять свои знания о back4app, вот несколько ценных ресурсов документация back4app документация back4app https //www back4app com/docs/get started/welcome руководство по javascript для parse https //docs parseplatform org/js/guide/ канал back4app на youtube https //www youtube com/c/back4app парс сервер и livequery документация parse server https //docs parseplatform org/parse server/guide/ руководство по livequery 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 удачного кодирования!