Project Templates
Social Network
How to Create a Social Media Platform: Step-by-Step Guide (2025)
66 分
はじめに このチュートリアルでは、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にデフォルトで既に存在します) バイオやアバターなどの追加フィールドで拡張されます 投稿 テキストコンテンツや画像を含むユーザーの投稿を保存します コメント 投稿に対するコメントを保存します 会話 ユーザー間のチャット会話を表します メッセージ 会話内の個々のメッセージ typingstatus ユーザーが会話中に入力しているときの状態を追跡します データベースクラスの作成 back4appデータベースにこれらのクラスを作成しましょう back4appダッシュボードの「データベース」セクションに移動します ユーザークラスの拡張 既存の「ユーザー」クラスをクリックしてください 次の列を追加します バイオ (タイプ 文字列) アバター (タイプ ファイル) フォロワー (タイプ 数字, デフォルト 0) 次の (タイプ 数字, デフォルト 0) postクラスの作成 「クラスを作成」をクリック 「post」とクラス名を入力し、「空のクラスを作成」を選択します 次の列を追加します コンテンツ (タイプ 文字列) 著者 (タイプ userへのポインタ) 画像 (タイプ ファイル) いいね (タイプ 数字, デフォルト 0) いいねした人 (タイプ 配列) 作成日時 (タイプ 日付、自動的に追加されます) コメントクラスの作成 "コメント"という名前の新しいクラスを作成し、次の列を含めます コンテンツ (タイプ 文字列) 著者 (タイプ userへのポインタ) 投稿(タイプ:投稿へのポインタ) 作成日時 (タイプ 日付、自動的に追加されます) 会話クラスの作成 "conversation"という名前の新しいクラスを作成し、次の列を含めます 参加者 (タイプ 配列) 最後のメッセージ (タイプ 文字列) 更新日時 (タイプ 日付、自動的に追加されます) メッセージクラスの作成 "メッセージ"という名前の新しいクラスを作成し、次の列を含めます テキスト(タイプ:文字列) 送信者 (タイプ userへのポインタ) 会話 (タイプ 会話へのポインタ) 作成日時 (タイプ 日付、自動的に追加されます) typingstatusクラスの作成 "typingstatus"という名前の新しいクラスを作成し、次の列を含めます ユーザー (タイプ user へのポインタ) 会話 (タイプ 会話へのポインタ) 入力中 (タイプ ブール) クラスの権限を設定する アプリケーションデータを保護するために、各クラスに適切なアクセス制御リスト(acl)を設定してください back4appダッシュボードの「セキュリティとキー」セクションに移動します 「クラスレベルのセキュリティ」の下で、各クラスの権限を設定します 例えば、「 post class 」の場合 公開読み取りアクセス 有効(誰でも投稿を見ることができます) 公開書き込みアクセス 有効(認証されたユーザーが投稿を作成できます) 更新/削除のための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 serverは、 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; } }; メッセージのための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」>「cloud functions」に移動します 上記のコードで新しい関数を作成します 関数をデプロイします 高度な検索のためのクラウドコード より複雑な検索機能を作成するには、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 フックの使用 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); } }; インデックスを作成するための主要なフィールド author クラスpost内で、ユーザー特有のクエリを高速化するため createdat タイムラインの読み込みを高速化するため username クラスuser内で、ユーザー検索を高速化するため participants クラス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を設定する 特に画像やメディアファイルにとって重要 セキュリティの最適化 セキュリティとパフォーマンスは密接に関連しています。これらの最適化は両方を改善します ポインタ権限を実装する ポインタ権限を使用して関連オブジェクトへのアクセスを制限する これにより、セキュリティを犠牲にすることなく、より効率的なクエリが可能になる // 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); } }; parse aclを効率的に使用する 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サーバーの最適化 back4appでparseサーバーの設定を最適化します サーバーパラメータを設定 「アプリ設定」>「サーバー設定」に移動 長時間実行される操作のタイムアウト設定を調整 最大リクエスト制限を設定 レート制限を設定 悪用を防ぎ、公平な使用を確保するためにレート制限を実装 前述のようにcloud codeを通じて設定 これらのパフォーマンス最適化戦略を実装することで、あなたのソーシャルネットワークアプリケーションは成長に対応し、ユーザーベースが拡大しても優れたパフォーマンスを維持するための良い位置に置かれます。アプリケーションのパフォーマンスを継続的に監視し、必要に応じて段階的な改善を行うことを忘れないでください。 結論 このチュートリアルでは、back4appをバックエンドサービスとして使用して、完全なソーシャルネットワークアプリケーションを構築する方法を学びました。以下のようなコア機能を実装しました ユーザー認証とプロフィール管理 投稿作成とソーシャルインタラクション livequeryを使用したリアルタイムメッセージング 検索機能 cloud functionsやバックグラウンドジョブなどの高度な機能 back4appのparse serverは、強力でスケーラブルなバックエンドソリューションを提供し、複雑なサーバーインフラストラクチャを心配することなく、優れたユーザーエクスペリエンスの構築に集中できるようにします。 次のステップ ソーシャルネットワークアプリケーションをさらに強化するために、以下を検討してください プッシュ通知の実装 back4appのプッシュサービスを使用して 分析の追加 ユーザーエンゲージメントを追跡するために ファイルストレージオプションの設定 大きなメディアファイル用に コンテンツモデレーションの実装 cloud functionsを使用して モバイルアプリの作成 同じback4appバックエンドを使用してreact nativeで back4appの機能を活用し、このチュートリアルで説明した最適化戦略に従うことで、数千のユーザーを処理しながら高いパフォーマンスを維持するソーシャルネットワークを構築できます。 追加リソース back4appについての知識をさらに広げるために、以下の貴重なリソースがあります back4app ドキュメント back4app ドキュメント https //www back4app com/docs/get started/welcome parse javascript ガイド https //docs parseplatform org/js/guide/ back4appのyoutubeチャンネル 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を使って素晴らしいアプリケーションを構築するための知識と自信を提供できたことを願っています。コーディングを楽しんでください!