Project Templates
Social Network
Reactを使ったソーシャルネットワークの構築方法
113 分
イントロダクション このチュートリアルでは、instagramに似たフル機能のソーシャルネットワークプラットフォームback4gramを構築します。back4gramでは、ユーザーがアカウントを作成し、画像付きの投稿を共有し、いいねやコメントを通じて相互作用し、コンテンツを検索し、リアルタイムメッセージングでコミュニケーションを取ることができます。このプロジェクトは、reactの強力なフロントエンド機能とback4appの堅牢なバックエンドサービスを組み合わせて、現代的で機能豊富なソーシャルアプリケーションを作成する方法を示しています。 reactは、再利用可能なui要素と効率的なレンダリングを可能にするコンポーネントベースのアーキテクチャを持っているため、ソーシャルネットワークのフロントエンドに最適な選択肢です。一方、back4appは、ユーザー認証、データストレージ、ファイルアップロード、リアルタイム機能を処理する管理されたparse serverバックエンドを提供し、複雑なサーバーインフラをゼロから構築する必要がありません。 このチュートリアルを完了することで、次の機能を持つ完全なソーシャルネットワークを構築します: ユーザー認証(サインアップ、ログイン、パスワードリセット) プロフィール管理 画像アップロードを伴う投稿作成 ソーシャルインタラクション(いいね、コメント) タイピングインジケーター付きのリアルタイムメッセージング コンテンツ検索機能 ユーザー設定と好み その過程で、次のような貴重なスキルを身につけることができます: フックとコンテキストを使用したreact開発 chakra uiを使用したui開発 back4appを通じたparse server統合 livequeryを使用したリアルタイムデータ管理 ユーザー認証フロー ファイルアップロード処理 レスポンシブデザインの実装 あなたが自分のソーシャルプラットフォームを立ち上げようとしているのか、単に現代のソーシャルネットワークがどのように構築されているのかを理解したいのかにかかわらず、このチュートリアルはあなたの目標を達成するための知識と実践的な経験を提供します。 いつでも、完全なコードにアクセスできます。 github 。 前提条件 このチュートリアルを完了するには、次のものが必要です: back4appアカウント 無料アカウントにサインアップするには、 back4app com https //www back4app com/ これを使用してバックエンドサービスを作成および管理します ローカルマシンにnode jsとnpmがインストールされている node js(バージョン14 x以降)とnpmをインストールします nodejs org https //nodejs org/ インストールを確認するには、 node v と npm v をターミナルで実行してください reactの基本的な理解 reactコンポーネント、フック、およびjsxに関する知識 reactを復習する必要がある場合は、 公式のreactドキュメント https //reactjs org/docs/getting started html コードエディタ visual studio code、sublime text、またはatomのような現代的なコードエディタ git(オプション) バージョン管理とリポジトリに沿って進むために 補足リソース back4app ドキュメント https //www back4app com/docs/get started/welcome parse javascript ガイド https //docs parseplatform org/js/guide/ chakra ui ドキュメント https //chakra ui com/docs/getting started react router ドキュメント https //reactrouter com/en/main ステップ 1 — back4app バックエンドの設定 このステップでは、新しいback4appプロジェクトを作成し、ソーシャルネットワークアプリケーションに必要なデータベーススキーマを構成します。back4appは、ユーザー認証、データストレージ、リアルタイム機能を処理する管理されたparse serverを提供します。 新しいback4appプロジェクトの作成 back4appアカウントにログインし、ダッシュボードに移動します。 「新しいアプリを作成」ボタンをクリックします。 アプリ名に「back4gram」と入力し、最も近いサーバー地域を選択して「作成」をクリックします。 アプリが作成されると、アプリダッシュボードにリダイレクトされます。 データベーススキーマの理解 データベースにクラスを作成する前に、私たちのソーシャルネットワークに必要なデータモデルを理解しましょう。アプリケーションの要件に基づいて、以下のクラスが必要になります: ユーザー (parseにデフォルトで既に存在します) このクラスはユーザー認証とプロフィール情報を処理します バイオやアバターなどの追加フィールドを使って拡張します 投稿 ユーザーの投稿をテキストコンテンツと画像を含めて保存します フィールド コンテンツ (文字列), 著者 (ユーザーへのポインタ), 画像 (ファイル), いいね (数), コメント (配列), 作成日時 (日付) コメント 投稿に対するコメントを保存する フィールド コンテンツ (文字列), 著者 (ユーザーへのポインタ), 投稿 (投稿へのポインタ), 作成日時 (日付) 会話 ユーザー間のチャット会話を表します フィールド 参加者(ユーザーへのポインタの配列)、最終メッセージ(文字列)、更新日時(日付) メッセージ 会話内の個々のメッセージ フィールド text (文字列), sender (ユーザーへのポインタ), conversation (会話へのポインタ), createdat (日付) タイピングステータス (リアルタイムのタイピングインジケーター用) ユーザーが会話中に入力しているときのトラッキング フィールド user(ユーザーへのポインタ)、conversation(会話へのポインタ)、istyping(ブール値) データベースクラスの作成 では、back4appデータベースにこれらのクラスを作成しましょう: back4appダッシュボードの「データベース」セクションに移動します。 ユーザークラスの拡張 既存の「ユーザー」クラスをクリックしてください 次の列を追加します: バイオ (タイプ 文字列) アバター (タイプ ファイル) フォロワー (タイプ 数字, デフォルト 0) 次の (タイプ 数字, デフォルト 0) postクラスの作成 「クラスを作成」をクリック クラス名に「post」と入力し、「空のクラスを作成」を選択します 次の列を追加します: コンテンツ (タイプ 文字列) 著者 (タイプ userへのポインタ) 画像 (タイプ ファイル) いいね (タイプ 数字, デフォルト 0) コメント (タイプ 配列) 作成日時 (タイプ 日付、自動的に追加されます) コメントクラスの作成 「クラスを作成」をクリック 「コメント」としてクラス名を入力し、「空のクラスを作成」を選択します 次の列を追加します: コンテンツ (タイプ 文字列) 著者 (タイプ userへのポインタ) 投稿(タイプ:投稿へのポインタ) 作成日時 (タイプ 日付、自動的に追加されます) 会話クラスの作成 「クラスを作成」をクリック 「conversation」とクラス名を入力し、「空のクラスを作成」を選択します 次の列を追加します: 参加者 (タイプ 配列) 最後のメッセージ (タイプ 文字列) 更新日時 (タイプ 日付、自動的に追加) メッセージクラスの作成 「クラスを作成」をクリック 「メッセージ」とクラス名を入力し、「空のクラスを作成」を選択します 次の列を追加します: テキスト (タイプ 文字列) 送信者 (タイプ userへのポインタ) 会話 (タイプ 会話へのポインタ) 作成日時 (タイプ 日付、自動的に追加) typingstatusクラスの作成 「クラスを作成」をクリック 「typingstatus」としてクラス名を入力し、「空のクラスを作成」を選択します 次の列を追加します: ユーザー (タイプ user へのポインタ) 会話 (タイプ 会話へのポインタ) 入力中 (タイプ ブール) クラスの権限の設定(オプション) データセキュリティを確保するために、各クラスに適切なアクセス制御リスト(acl)を構成する必要があります: back4appダッシュボードの「セキュリティとキー」セクションに移動します。 「クラスレベルセキュリティ」の下で、次の権限を設定します: ユーザークラス 公開読取アクセス 有効 (ユーザーが他のユーザーのプロフィールを見ることができるように) 公開書き込みアクセス 無効 (ユーザーは自分のプロフィールのみを変更できます) クラスの投稿 公開読取アクセス 有効 (誰でも投稿を見ることができます) 公開書き込みアクセス 有効 (認証されたユーザーは投稿を作成できます) 更新/削除のためのclpを追加して、著者のみに制限する コメントクラス 公開読取アクセス 有効 (誰でもコメントを見ることができます) 公開書き込みアクセス 有効 (認証されたユーザーはコメントを作成できます) 更新/削除のためのclpを追加して、著者のみに制限する 会話クラス 公開読取アクセス 無効 (会話はプライベートです) 公開書き込みアクセス 有効 (認証されたユーザーは会話を作成できます) 会話参加者への読み取り/書き込みアクセスを制限するclpを追加する メッセージクラス 公開読取アクセス 無効 (メッセージはプライベートです) 公開書き込みアクセス:有効(認証されたユーザーはメッセージを送信できます) 会話参加者への読み取り/書き込みアクセスを制限するclpを追加する typingstatusクラス 公開読取アクセス 無効 (入力状況はプライベートです) 公開書き込みアクセス 有効 (認証されたユーザーはタイピングステータスを更新できます) 会話参加者への読み取り/書き込みアクセスを制限するclpを追加する リアルタイム機能のためのlivequeryの設定 メッセージングやタイピングインジケーターなどのリアルタイム機能を有効にするには、livequeryを設定する必要があります。 back4appダッシュボードの「サーバー設定」セクションに移動します。 「parse server」の下にある「livequery」セクションを見つけて、それを有効にします。 livequeryによって監視される以下のクラスを追加します: メッセージ タイピングステータス 投稿(いいねやコメントのリアルタイム更新用) 変更を保存します。 アプリケーションキーの取得 reactフロントエンドをバックエンドに接続するには、back4appアプリケーションキーが必要です。 「アプリ設定」>「セキュリティとキー」セクションに移動します。 次のキーをメモします: アプリケーションid javascriptキー サーバーurl livequeryサーバーurl(サブドメイン設定、リアルタイム機能用) これらのキーを使用して、reactアプリケーションでparseを初期化します。 ステップ2 — reactフロントエンドプロジェクトの作成 このステップでは、新しいreactプロジェクトを設定し、back4appバックエンドと連携するように構成します。必要な依存関係をインストールし、プロジェクト構造を作成し、parseサーバーに接続します。 新しいreactプロジェクトの設定 新しいreactアプリケーションをcreate react appを使用して作成することから始めましょう。これにより、設定なしで現代的なビルドセットアップが提供されます。 ターミナルを開き、プロジェクトを作成したいディレクトリに移動します。 新しいreactアプリケーションを作成するために、次のコマンドを実行します: npx create react app back4gram プロジェクトが作成されたら、プロジェクトディレクトリに移動します: cd back4gram 開発サーバーを起動して、すべてが正常に動作していることを確認します: npm start これにより、ブラウザで新しいreactアプリケーションが開きます。 http //localhost 3000 http //localhost 3000 。 必要な依存関係のインストール さて、ソーシャルネットワークアプリケーションに必要なパッケージをインストールしましょう: 開発サーバーを停止します(ターミナルでctrl+cを押します)。 back4appに接続するためのparse sdkをインストールします: npm install parse ナビゲーションのためにreact routerをインストールします: npm install react router dom ユーザーインターフェースコンポーネントのためにchakra uiをインストールします: npm install @chakra ui/react @emotion/react @emotion/styled framer motion 追加のuiユーティリティとアイコンライブラリをインストールします: npm install react icons プロジェクト構造の説明 明確な構造でプロジェクトを整理しましょう。以下のディレクトリを src フォルダーに作成します: mkdir p src/components/ui src/pages src/contexts src/utils 各ディレクトリの目的は次のとおりです: components 再利用可能なuiコンポーネント ui ボタン、フォーム、モーダルなどの基本的なuiコンポーネント 特定の機能(例:投稿、コメント)のための他のコンポーネントフォルダー pages ルートに対応するフルページコンポーネント contexts 状態管理のためのreactコンテキストプロバイダー utils ユーティリティ関数とヘルパー 環境変数の作成 back4appの認証情報を安全に保存するには、 env ファイルをプロジェクトのルートに作成します: プロジェクトのルートに新しいファイルを作成します: env local : touch env local ファイルを開いて、back4appの認証情報を追加します: react app parse app id=your application id react app parse js key=your javascript key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your app id back4app io プレースホルダーの値をステップ1からの実際のback4appの認証情報に置き換えます。 必ず、 env local を gitignore ファイルに追加して、機密情報がコミットされないようにします。 back4appの資格情報を使用してparse sdkを構成する では、parse sdkを設定してback4appバックエンドに接続しましょう 新しいファイルを作成します src/utils/parseconfig js // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // parseを初期化 parse initialize( process env react app parse app id, process env react app parse js key ); parse serverurl = process env react app parse server url; // 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 ファイルを更新してparse構成をインポートします import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import reportwebvitals from ' /reportwebvitals'; import ' /utils/parseconfig'; // parse構成をインポート const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); reportwebvitals(); ルーティングを使用したアプリコンポーネントの設定 ルーティングとchakra uiプロバイダーを含むように、メインのappコンポーネントを更新しましょう: 更新 src/app js import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider, extendtheme } from '@chakra ui/react'; // ページをインポートします(次に作成します) import landingpage from ' /pages/landingpage'; import loginpage from ' /pages/loginpage'; import signuppage from ' /pages/signuppage'; import resetpasswordpage from ' /pages/resetpasswordpage'; import feedpage from ' /pages/feedpage'; import profilepage from ' /pages/profilepage'; import postdetailspage from ' /pages/postdetailspage'; import messagespage from ' /pages/messagespage'; import searchpage from ' /pages/searchpage'; import settingspage from ' /pages/settingspage'; // カスタムテーマを作成します const theme = extendtheme({ config { initialcolormode 'dark', usesystemcolormode false, }, colors { brand { 50 '#e5f4ff', 100 '#b8dcff', 200 '#8ac5ff', 300 '#5cadff', 400 '#2e96ff', 500 '#147dff', 600 '#0061cc', 700 '#004799', 800 '#002d66', 900 '#001433', }, }, }); function app() { return ( \<chakraprovider theme={theme}> \<router> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/signup" element={\<signuppage />} /> \<route path="/reset password" element={\<resetpasswordpage />} /> \<route path="/feed" element={\<feedpage />} /> \<route path="/profile" element={\<profilepage />} /> \<route path="/post/\ id" element={\<postdetailspage />} /> \<route path="/messages" element={\<messagespage />} /> \<route path="/search" element={\<searchpage />} /> \<route path="/settings" element={\<settingspage />} /> \</routes> \</router> \</chakraprovider> ); } export default app; 保護されたルートコンポーネントの作成 認証が必要なルートを保護するために、protectedrouteコンポーネントを作成しましょう: まず、ユーザー認証状態を管理するためにauthcontextを作成します: // src/contexts/authcontext js import react, { createcontext, usestate, usecontext, useeffect } from 'react'; import parse from 'parse/dist/parse min js'; const authcontext = createcontext(); export function useauth() { return usecontext(authcontext); } export function authprovider({ children }) { const \[currentuser, setcurrentuser] = usestate(null); const \[isloading, setisloading] = usestate(true); useeffect(() => { // check if user is already logged in const checkuser = async () => { try { const user = await parse user current(); setcurrentuser(user); } catch (error) { console error('error checking current user ', error); } finally { setisloading(false); } }; checkuser(); }, \[]); // login function const login = async (username, password) => { try { const user = await parse user login(username, password); setcurrentuser(user); return user; } catch (error) { throw error; } }; // signup function const signup = async (username, email, password) => { try { const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); const result = await user signup(); setcurrentuser(result); return result; } catch (error) { throw error; } }; // logout function const logout = async () => { try { await parse user logout(); setcurrentuser(null); } catch (error) { throw error; } }; // reset password function const resetpassword = async (email) => { try { await parse user requestpasswordreset(email); } catch (error) { throw error; } }; const value = { currentuser, isloading, login, signup, logout, resetpassword, }; return \<authcontext provider value={value}>{children}\</authcontext provider>; } 次に、protectedrouteコンポーネントを作成します: // src/components/protectedroute js import react from 'react'; import { navigate } from 'react router dom'; import { useauth } from ' /contexts/authcontext'; import { flex, spinner } from '@chakra ui/react'; function protectedroute({ children }) { const { currentuser, isloading } = useauth(); if (isloading) { return ( \<flex justify="center" align="center" height="100vh"> \<spinner size="xl" /> \</flex> ); } if (!currentuser) { return \<navigate to="/login" />; } return children; } export default protectedroute; authproviderとprotectedrouteを使用するようにappコンポーネントを更新します // src/app js (updated) import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider, extendtheme } from '@chakra ui/react'; import { authprovider } from ' /contexts/authcontext'; import protectedroute from ' /components/protectedroute'; // import pages import landingpage from ' /pages/landingpage'; import loginpage from ' /pages/loginpage'; import signuppage from ' /pages/signuppage'; import resetpasswordpage from ' /pages/resetpasswordpage'; import feedpage from ' /pages/feedpage'; import profilepage from ' /pages/profilepage'; import postdetailspage from ' /pages/postdetailspage'; import messagespage from ' /pages/messagespage'; import searchpage from ' /pages/searchpage'; import settingspage from ' /pages/settingspage'; // theme configuration (same as before) const theme = extendtheme({ // theme configuration }); function app() { return ( \<chakraprovider theme={theme}> \<authprovider> \<router> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/signup" element={\<signuppage />} /> \<route path="/reset password" element={\<resetpasswordpage />} /> \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> \<route path="/profile" element={ \<protectedroute> \<profilepage /> \</protectedroute> } /> \<route path="/post/\ id" element={ \<protectedroute> \<postdetailspage /> \</protectedroute> } /> \<route path="/messages" element={ \<protectedroute> \<messagespage /> \</protectedroute> } /> \<route path="/search" element={ \<protectedroute> \<searchpage /> \</protectedroute> } /> \<route path="/settings" element={ \<protectedroute> \<settingspage /> \</protectedroute> } /> \</routes> \</router> \</authprovider> \</chakraprovider> ); } export default app; 基本的なランディングページの作成 セットアップをテストするために、シンプルなランディングページを作成しましょう: // src/pages/landingpage js import react from 'react'; import { box, heading, text, button, vstack, flex } from '@chakra ui/react'; import { link as routerlink } from 'react router dom'; function landingpage() { return ( \<box bg="gray 900" minh="100vh" color="white"> \<flex direction="column" align="center" justify="center" textalign="center" py={20} \> \<heading size="2xl" mb={4}> back4gram \</heading> \<text fontsize="lg" maxw="600px" mb={8}> join a vibrant community where your voice matters share stories, ideas, and moments with friends and the world \</text> \<vstack spacing={4} maxw="md" mx="auto"> \<button as={routerlink} to="/signup" colorscheme="brand" size="lg" w="full" \> create account \</button> \<button as={routerlink} to="/login" variant="outline" size="lg" w="full" \> log in \</button> \</vstack> \</flex> \</box> ); } export default landingpage; セットアップのテスト reactアプリケーションの基本構造を設定し、back4appに接続したので、テストしてみましょう: 開発サーバーを起動します: npm start ブラウザを開いて、 http //localhost 3000 http //localhost 3000 に移動します。サインアップまたはログインするためのボタンがあるランディングページが表示されるはずです。 parseの初期化に関連するエラーがないことを確認するために、ブラウザのコンソールを確認してください。 ステップ3 — 認証機能の実装 このステップでは、back4appのparse serverを使用して、ソーシャルネットワークアプリケーションのユーザー認証機能を実装します。parseの認証システムがどのように機能するかを調べ、ログイン、サインアップ、パスワードリセット機能を実装します。 parseユーザー認証システムの理解 back4appのparse serverは、 parse user クラスを通じて包括的なユーザー管理システムを提供します。parseの認証が私たちのアプリケーションでどのように機能するかを理解しましょう: parse userクラス この parse user クラスは、ユーザー管理のために特別に設計された parse object の特別なサブクラスです。私たちのback4gramアプリケーションでは、これを使用して: ユーザーの資格情報(ユーザー名、メール、パスワード)を保存する 認証状態を管理する セッショントークンを自動的に処理する 私たちの実装を見てみると、parse userクラスとのインタラクションの仕方がわかります: // from signuppage js const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); await user signup(); このコードは新しいparse userオブジェクトを作成し、必要なフィールドを設定し、signup()メソッドを呼び出してback4appにユーザーを登録します。 parseにおける認証フロー 私たちのアプリケーションにおける認証の仕組みを見てみましょう: 登録フロー 私たちのsignuppage jsでは、ユーザー名、メールアドレス、パスワードを収集します 入力データを検証します(空のフィールド、正しいメール形式、パスワードの長さをチェック) 新しい parse user オブジェクトを作成し、資格情報を設定します signup()を呼び出し、データをback4appに送信します パースはパスワードを保存する前にハッシュ化します 成功すると、ユーザーはセッショントークンで自動的にログインします ログインフロー 私たちのloginpage jsでは、ユーザー名とパスワードを収集します これらの資格情報でparse user login()を呼び出します パースは保存されたデータに対して資格情報を検証します 有効な場合、parseはセッショントークンを生成します セッショントークンはブラウザストレージに自動的に保存されます セッション管理 パースは自動的にすべてのapiリクエストにセッショントークンを含めます 現在ログインしているユーザーを取得するためにparse user current()を使用します セッションはページのリフレッシュを超えて持続します ユーザー登録の実装 ユーザー登録がどのように実装されているかを理解するために、signuppageコンポーネントを見てみましょう: フォームバリデーション back4appにデータを送信する前に、ユーザー入力を検証します: // from signuppage js const validateform = () => { const newerrors = {}; if (!username trim()) { newerrors username = 'username is required'; } if (!email trim()) { newerrors email = 'email is required'; } else if (!/\s+@\s+\\ \s+/ test(email)) { newerrors email = 'email is invalid'; } if (!password) { newerrors password = 'password is required'; } else if (password length < 6) { newerrors password = 'password must be at least 6 characters'; } if (password !== confirmpassword) { newerrors confirmpassword = 'passwords do not match'; } seterrors(newerrors); return object keys(newerrors) length === 0; }; この検証により、以下が保証されます: ユーザー名が空でない メールアドレスが有効である パスワードが6文字以上である パスワードと確認が一致する 登録エラーの処理 私たちのサインアップハンドラーには、parse特有のエラー処理が含まれています: // from signuppage js try { // create a new user const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); await user signup(); toaster create({ title 'success', description 'account created successfully!', type 'success', }); navigate('/feed'); } catch (error) { toaster create({ title 'signup failed', description error message, type 'error', }); // handle specific parse errors if (error code === 202) { seterrors({ errors, username 'username already taken'}); } else if (error code === 203) { seterrors({ errors, email 'email already in use'}); } } back4appは、ユーザーに役立つフィードバックを提供するために使用できる特定のエラーコードを返します: コード 202 ユーザー名はすでに使用されています コード 203 メールアドレスはすでに使用されています ユーザー登録/サインアップの完全なコードはここにあります。 ユーザーログインの実装 私たちのloginpageコンポーネントは、parse user login()を使用してユーザー認証を処理します: ログインフォーム ログインフォームはユーザー名とパスワードを収集します: // from loginpage js \<form onsubmit={handlelogin}> \<vstack spacing={4}> \<field label="username"> \<input type="text" value={username} onchange={(e) => setusername(e target value)} placeholder="your username" required /> \</field> \<field label="password" errortext={error} \> \<input type="password" value={password} onchange={(e) => setpassword(e target value)} placeholder="your password" required /> \</field> \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> \<button colorscheme="blue" width="full" type="submit" loading={isloading} \> log in \</button> \</vstack> \</form> セッション確認 前述のように、ログインページが読み込まれるときに既存のセッションを確認します: // from loginpage js useeffect(() => { const checkcurrentuser = async () => { try { const user = await parse user current(); if (user) { setcurrentuser(user); navigate('/feed'); } } catch (error) { console error('error checking current user ', error); } }; checkcurrentuser(); }, \[navigate]); これはparseの重要な機能です ブラウザストレージ内のセッショントークンを自動的に管理し、ユーザーがすでにログインしているかどうかを簡単に確認できます。 パスワードリセットの実装 back4appは組み込みのパスワードリセットフローを提供します。私たちのアプリケーションでは、ログインフォームからパスワードリセットページにリンクします: // from loginpage js \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> back4appにおけるパスワードリセットプロセスは次のように機能します: ユーザーがメールでパスワードリセットをリクエストします parseがユーザーのメールに特別なリセットリンクを送信します ユーザーがリンクをクリックして新しいパスワードを設定します parseがデータベース内のパスワードハッシュを更新します これを私たちのアプリケーションに実装するには、次のようにします: // example password reset implementation try { await parse user requestpasswordreset(email); // show success message } catch (error) { // handle error } 認証されたユーザーのためのルートの保護 アプリケーション内の特定のルートを保護するために、ユーザーが認証されているかどうかを確認するprotectedrouteコンポーネントを使用します: // from protectedroute js function protectedroute({ children }) { const { currentuser, isloading } = useauth(); if (isloading) { return ( \<flex justify="center" align="center" height="100vh"> \<spinner size="xl" /> \</flex> ); } if (!currentuser) { return \<navigate to="/login" />; } return children; } このコンポーネント: ユーザーがログインしているかどうかを確認するためにauthcontextを使用します 確認中にローディングスピナーを表示します ユーザーが見つからない場合はログインページにリダイレクトします ユーザーが認証されている場合、保護されたコンテンツをレンダリングします このコンポーネントをルーティング設定で使用します: // from app js \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> back4app 認証設定 back4app はダッシュボードで認証のためのいくつかの設定オプションを提供します: メール認証 ユーザーがログインする前にメール認証を要求できます これを「サーバー設定」>「parse server」>「ユーザー認証」で設定します パスワードポリシー 最小パスワード長と複雑さの要件を設定します これを「サーバー設定」>「parse server」>「ユーザー認証」で設定します セッションの長さ ユーザーセッションが有効な期間を制御します これを「サーバー設定」>「parse server」>「セッション設定」で設定します メールテンプレート 認証とパスワードリセットのメールをカスタマイズします これを「アプリ設定」>「メールテンプレート」で設定します 認証実装のテスト 認証システムが正しく機能することを確認するために: ユーザー登録のテスト 有効な資格情報で登録を試みる 既存のユーザー名で登録を試みる(エラーが表示されるはず) ユーザーが「 user」クラスの下でback4appダッシュボードに表示されるか確認する ユーザーログインのテスト 正しい資格情報でログインを試みる(フィードにリダイレクトされるはず) 不正な資格情報でログインを試みる(エラーが表示されるはず) セッションの持続性のテスト ログインしてページをリフレッシュする(ログインしたままであるべき) ブラウザを閉じて再度開く(セッションが有効であればログインしたままであるべき) 保護されたルートのテスト ログアウトしているときに/feedにアクセスを試みる(ログインページにリダイレクトされるはず) ログインしているときに/feedにアクセスを試みる(フィードページが表示されるはず) ログインコンポーネントのコードはここにあります。 ステップ 4 — フィード機能の開発 このステップでは、コアのソーシャルネットワーキング機能であるフィードを実装します。ここでは、ユーザーが投稿を作成し、他の人のコンテンツを表示し、いいねやコメントを通じて相互作用します。back4appのparse serverを使用して投稿を保存および取得し、画像のファイルアップロードを処理し、リアルタイムの更新を実装します。 フィードページの構造を理解する 私たちのアプリケーションのフィードページには、3つの主要なコンポーネントがあります: ナビゲーション用のサイドバー 投稿作成と投稿リスト表示のためのメインフィードエリア トレンドセクション(大きな画面で) これが私たちのfeedpage jsでどのように実装されているかを見てみましょう: // from feedpage js main structure function feedpage() { // state and hooks return ( \<flex minh="100vh" bg="gray 800" color="white"> {/ left sidebar (navigation) /} \<box w={\['0px', '200px']} bg="gray 900" p={4} display={\['none', 'block']}> {/ navigation links /} \</box> {/ main content (feed) /} \<box flex="1" p={4} overflowy="auto"> {/ post creation form /} {/ posts list /} \</box> {/ right sidebar (trending) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']}> {/ trending content /} \</box> \</flex> ); } このレスポンシブレイアウトは、異なる画面サイズに適応し、モバイルデバイスではサイドバーを隠します。 back4appでの投稿クラスの作成 フロントエンドを実装する前に、back4appデータベースが投稿用に適切に設定されていることを確認しましょう: postクラスには以下のフィールドが必要です: コンテンツ (文字列):投稿のテキストコンテンツ 画像 (ファイル):オプションの画像添付 著者 (ユーザーへのポインタ):投稿を作成したユーザー いいね (数):投稿のいいねの数 いいねしたユーザー (配列):投稿をいいねしたユーザーidの配列 作成日時 (日付):parseによって自動的に追加される postクラスに適切な権限を設定します: 公開読取アクセス:誰でも投稿を読むことができるべきです 公開書き込みアクセス:認証されたユーザーは投稿を作成できるべきです 更新/削除権限:著者のみが自分の投稿を修正できるべきです 投稿作成の実装 私たちのfeedpageコンポーネントでの投稿作成がどのように実装されているかを見てみましょう: // from feedpage js post creation state const \[postcontent, setpostcontent] = usestate(''); const \[postimage, setpostimage] = usestate(null); const \[imagepreview, setimagepreview] = usestate(''); const \[issubmitting, setissubmitting] = usestate(false); // image selection handler const handleimageselect = (e) => { if (e target files && e target files\[0]) { const file = e target files\[0]; setpostimage(file); // create preview url const previewurl = url createobjecturl(file); setimagepreview(previewurl); } }; // post submission handler const handlesubmitpost = async () => { if (!postcontent trim() && !postimage) { toaster create({ title 'error', description 'please add some text or an image to your post', type 'error', }); return; } setissubmitting(true); try { // create a new post object const post = parse object extend('post'); const newpost = new post(); // set post content newpost set('content', postcontent); newpost set('author', parse user current()); newpost set('likes', 0); newpost set('likedby', \[]); // if there's an image, upload it if (postimage) { const parsefile = new parse file(postimage name, postimage); await parsefile save(); newpost set('image', parsefile); } // save the post await newpost save(); // reset form setpostcontent(''); setpostimage(null); setimagepreview(''); // refresh posts fetchposts(); toaster create({ title 'success', description 'your post has been published!', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } finally { setissubmitting(false); } }; 投稿作成に関する重要なポイント: parseにおけるファイル処理 parse fileはback4appのストレージに画像をアップロードするために使用されます ファイルは最初に保存され、その後投稿オブジェクトに添付されます back4appは自動的にファイルストレージを処理し、urlを生成します parseオブジェクトの作成 'post'クラスを拡張します parse object extend('post') 新しいインスタンスを作成します new post() set()メソッドを使用してプロパティを設定します set() メソッド オブジェクトをback4appに保存します save() ユーザーの関連付け 現在のユーザーと投稿を関連付けます parse user current() これにより、データベース内にポインタ関係が作成されます 投稿作成フォームのuiはこのようになります: {/ post creation form /} \<box mb={6} bg="gray 700" p={4} borderradius="md"> \<vstack align="stretch" spacing={4}> \<textarea placeholder="what's on your mind?" value={postcontent} onchange={(e) => setpostcontent(e target value)} minh="100px" /> {imagepreview && ( \<box position="relative"> \<image src={imagepreview} maxh="200px" borderradius="md" /> \<iconbutton icon={\<closeicon />} size="sm" position="absolute" top="2" right="2" onclick={() => { setpostimage(null); setimagepreview(''); }} /> \</box> )} \<hstack> \<button lefticon={\<attachmenticon />} onclick={() => document getelementbyid('image upload') click()} \> add image \</button> \<input id="image upload" type="file" accept="image/ " onchange={handleimageselect} display="none" /> \<button colorscheme="blue" ml="auto" isloading={issubmitting} onclick={handlesubmitpost} disabled={(!postcontent trim() && !postimage) || issubmitting} \> post \</button> \</hstack> \</vstack> \</box> 投稿の取得と表示 では、back4appから投稿を取得して表示する方法を見てみましょう: // from feedpage js fetching posts const \[posts, setposts] = usestate(\[]); const \[isloading, setisloading] = usestate(true); const \[page, setpage] = usestate(0); const \[hasmore, sethasmore] = usestate(true); const postsperpage = 10; const fetchposts = async (loadmore = false) => { try { const currentpage = loadmore ? page + 1 0; // create a query for the post class const post = parse object extend('post'); const query = new parse query(post); // include the author object (pointer) query include('author'); // sort by creation date, newest first query descending('createdat'); // pagination query limit(postsperpage); query skip(currentpage postsperpage); // execute the query const results = await query find(); // process the results const fetchedposts = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); // update state if (loadmore) { setposts(prevposts => \[ prevposts, fetchedposts]); setpage(currentpage); } else { setposts(fetchedposts); setpage(0); } // check if there are more posts to load sethasmore(results length === postsperpage); } catch (error) { console error('error fetching posts ', error); toaster create({ title 'error', description 'failed to load posts please try again ', type 'error', }); } finally { setisloading(false); } }; // load posts when component mounts useeffect(() => { fetchposts(); }, \[]); 投稿を取得する際の重要なポイント: クエリの解析 新しいクエリを作成します new parse query(post) 関連オブジェクトを含めます query include('author') ソートします query descending('createdat') ページネーションを行います query limit() と query skip() クエリを実行します query find() 結果の処理 parseオブジェクトには get() メソッドがあり、プロパティにアクセスできます ファイルフィールドの場合、 file url() を使用してurlを取得します parseオブジェクトをプレーンなjavascriptオブジェクトに変換してreactの状態にします ページネーション ページ追跡を使用して「もっと読み込む」機能を実装します 追加のリクエストを行う前に、読み込む投稿があるかどうかを確認します 投稿はリストに表示されます: {/ posts list /} {isloading ? ( \<center py={10}> \<spinner size="xl" /> \</center> ) posts length > 0 ? ( \<vstack spacing={4} align="stretch"> {posts map(post => ( \<box key={post id} p={4} bg="gray 700" borderradius="md"> {/ post header with author info /} \<hstack mb={2}> \<avatar root size="sm"> \<avatar fallback name={post author username} /> \<avatar image src={post author avatar} /> \</avatar root> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(post createdat)}\</text> \</hstack> {/ post content /} \<text mb={4}>{post content}\</text> {/ post image if any /} {post image && ( \<image src={post image} maxh="400px" borderradius="md" mb={4} /> )} {/ post actions /} \<hstack> \<button variant="ghost" lefticon={\<likeicon />} onclick={() => handlelikepost(post id, post likedby)} color={post likedby includes(currentuser id) ? "blue 400" "white"} \> {post likes} likes \</button> \<button variant="ghost" lefticon={\<commenticon />} as={routerlink} to={`/post/${post id}`} \> comments \</button> \</hstack> \</box> ))} {/ load more button /} {hasmore && ( \<button onclick={() => fetchposts(true)} isloading={isloadingmore}> load more \</button> )} \</vstack> ) ( \<center py={10}> \<text>no posts yet be the first to post!\</text> \</center> )} いいね機能の実装 いいね機能がどのように実装されているかを見てみましょう: // from feedpage js like functionality const handlelikepost = async (postid, likedby) => { try { const currentuserid = parse user current() id; const isliked = likedby includes(currentuserid); // get the post object const post = parse object extend('post'); const query = new parse query(post); const post = await query get(postid); // update likes count and likedby array if (isliked) { // unlike remove user from likedby and decrement likes post set('likedby', likedby filter(id => id !== currentuserid)); post set('likes', (post get('likes') || 1) 1); } else { // like add user to likedby and increment likes post set('likedby', \[ likedby, currentuserid]); post set('likes', (post get('likes') || 0) + 1); } // save the updated post await post save(); // update local state setposts(prevposts => prevposts map(p => p id === postid ? { p, likes isliked ? p likes 1 p likes + 1, likedby isliked ? p likedby filter(id => id !== currentuserid) \[ p likedby, currentuserid] } p ) ); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'failed to like post please try again ', type 'error', }); } }; いいね機能に関する重要なポイント: 楽観的更新 サーバーが変更を確認する前に、uiを即座に更新します これにより、アプリがより応答性を持つように感じられます オブジェクトの更新を解析 特定の投稿を取得します query get(postid) そのプロパティを変更します post set() 変更を保存します post save() いいねの追跡 カウント( いいね )とユーザーのリスト( likedby )の両方を維持します これにより、正確なカウントを表示し、現在のユーザーが投稿をいいねしたかどうかを判断できます livequeryを使用したリアルタイム更新の実装(オプション) 新しい投稿が作成されるとフィードがリアルタイムで更新されるようにするために、parse livequeryを使用できます。 // from feedpage js livequery setup const livequerysubscription = useref(null); useeffect(() => { // set up livequery for real time updates const setuplivequery = async () => { try { const post = parse object extend('post'); const query = new parse query(post); // subscribe to new posts livequerysubscription current = await query subscribe(); // when a new post is created livequerysubscription current on('create', (post) => { // only add to feed if it's not already there setposts(prevposts => { if (prevposts some(p => p id === post id)) return prevposts; const newpost = { id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } }; return \[newpost, prevposts]; }); }); // when a post is updated (e g , liked) livequerysubscription current on('update', (post) => { setposts(prevposts => prevposts map(p => p id === post id ? { p, content post get('content'), image post get('image') ? post get('image') url() p image, likes post get('likes') || 0, likedby post get('likedby') || \[] } p ) ); }); } catch (error) { console error('error setting up livequery ', error); } }; setuplivequery(); // clean up subscription when component unmounts return () => { if (livequerysubscription current) { livequerysubscription current unsubscribe(); } }; }, \[]); livequeryに関する重要なポイント: サブスクリプションの設定 クエリを作成し、 query subscribe() これにより、back4appのlivequeryサーバーへのwebsocket接続が確立されます イベント処理 新しい投稿が作成されるときに 'create' イベントをリッスンします 投稿が修正されるときに 'update' イベントをリッスンします それに応じてローカル状態を更新します クリーンアップ メモリリークを防ぐためにコンポーネントがアンマウントされるときにサブスクリプションを解除します ページネーションによる投稿の読み込みの最適化 すでに「もっと読み込む」ボタンで基本的なページネーションを実装しています。無限スクロールで強化しましょう: // from feedpage js infinite scrolling const \[isloadingmore, setisloadingmore] = usestate(false); const feedref = useref(null); // intersection observer for infinite scrolling useeffect(() => { if (!hasmore) return; const observer = new intersectionobserver( (entries) => { if (entries\[0] isintersecting && !isloading && !isloadingmore) { loadmoreposts(); } }, { threshold 0 5 } ); const loadmoretrigger = document getelementbyid('load more trigger'); if (loadmoretrigger) { observer observe(loadmoretrigger); } return () => { if (loadmoretrigger) { observer unobserve(loadmoretrigger); } }; }, \[hasmore, isloading, isloadingmore]); const loadmoreposts = async () => { if (!hasmore || isloadingmore) return; setisloadingmore(true); try { await fetchposts(true); } finally { setisloadingmore(false); } }; そして、投稿リストの最後にこれを追加します: {/ infinite scroll trigger /} {hasmore && ( \<box id="load more trigger" h="20px" /> )} 無限スクロールに関する重要なポイント: インターセクションオブザーバー ユーザーがスクロールして底に達したときに、インターセクションオブザーバーapiを使用して検出します トリガー要素が表示されると、さらに投稿を読み込みます 読み込み状態 初回読み込みと「さらに読み込む」のために別々の読み込み状態を追跡します これにより、同時に複数のリクエストが発生するのを防ぎます パフォーマンスの考慮事項 一度に固定数の投稿のみを読み込みます(ページネーション) 追加のリクエストを行う前に、読み込むべき投稿があるかどうかを確認します back4appパフォーマンス最適化 back4appを使用する際のパフォーマンス最適化: インデックスを使用する back4appダッシュボードで頻繁にクエリされるフィールドにインデックスを追加する postクラスの場合、'createdat'と'author'にインデックスを追加する 選択的クエリ 必要なフィールドのみを取得するために、 query select() を使用する これによりデータ転送が削減され、パフォーマンスが向上する カウント最適化 すべての投稿を取得してカウントするのではなく、 query count() これにより、合計カウントを決定するのがより効率的になる ステップ6 — ソーシャルインタラクションの追加 このステップでは、投稿へのコメント、ユーザープロフィール、ユーザー設定などの重要なソーシャルインタラクション機能を実装することで、私たちのソーシャルネットワークを強化します。これらの機能がback4appバックエンドとどのように相互作用し、機能するメカニズムに焦点を当てます。 投稿へのコメントの実装 コメントは、back4appで適切なデータモデリングを必要とする基本的なソーシャルインタラクション機能です。私たちのアプリケーションがparse serverとどのように相互作用してコメントを実装するかを見てみましょう: コメントのためのback4appデータモデル back4appでは、コメントはユーザーと投稿の両方に関係する別のクラスとして実装されます: コメントクラスの構造 内容 (文字列) コメントのテキスト内容 著者 (ユーザーへのポインタ) コメントを作成したユーザーを指します 投稿 (投稿へのポインタ) コメントされている投稿を指します 作成日時 (日付) parseによって自動的に管理されます 関係の種類 ユーザー → コメント 一対多(1人のユーザーが多くのコメントを作成できる) 投稿 → コメント 一対多(1つの投稿に多くのコメントがあることができる) back4appからのコメントの取得 私たちのpostdetailspageは、特定の投稿のコメントを取得するためにparseクエリを使用します。 // from postdetailspage js comment fetching const fetchcomments = async () => { try { // create a query on the comment class const comment = parse object extend('comment'); const query = new parse query(comment); // find comments for this specific post using a pointer equality constraint query equalto('post', postobject); // include the author information query include('author'); // sort by creation date (newest first) query descending('createdat'); // execute the query const results = await query find(); // transform parse objects to plain objects for react state const commentslist = results map(comment => ({ id comment id, content comment get('content'), createdat comment get('createdat'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null } })); setcomments(commentslist); } catch (error) { console error('error fetching comments ', error); toaster create({ title 'error', description 'failed to load comments', type 'error', }); } }; back4appの主要なメカニズム: parse object extend() back4appのcommentクラスへの参照を作成します。 query equalto() 特定の投稿のコメントのみを見つけるための制約を作成します。 query include() 単一のクエリで関連オブジェクトを取得するための結合のような操作を行います。 query descending() 特定のフィールドで結果をソートします。 back4appでのコメント作成 ユーザーがコメントを追加すると、新しいparseオブジェクトを作成し、関係を確立します。 // from postdetailspage js adding a comment const handleaddcomment = async (e) => { e preventdefault(); if (!newcomment trim()) { return; } setiscommenting(true); try { // create a new comment object in back4app const comment = parse object extend('comment'); const comment = new comment(); // set comment data and relationships comment set('content', newcomment); comment set('author', parse user current()); // pointer to current user comment set('post', postobject); // pointer to current post // save the comment to back4app await comment save(); // clear the input setnewcomment(''); // refresh comments fetchcomments(); toaster create({ title 'success', description 'your comment has been added', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } finally { setiscommenting(false); } }; back4appの主要なメカニズム: new comment() commentクラスの新しいインスタンスを作成します comment set() 関連オブジェクトへのポインタを含むparseオブジェクトのプロパティを設定します comment save() オブジェクトをback4appに保存するために送信します parse user current() 著者関係を確立するために現在認証されているユーザーを取得します back4appのコメントセキュリティ back4appでコメントを適切に保護するには: クラスレベルの権限(clp)を設定する: 読み取り:公開(誰でもコメントを読むことができる) 書き込み:認証されたユーザーのみ(ログインしたユーザーのみがコメントできる) 更新/削除:作成者のみ(コメントの作成者のみが修正または削除できる) これらの権限をback4appダッシュボードで設定します: { "find" { " " true }, "get" { " " true }, "create" { " " true }, "update" { "requiresauthentication" true }, "delete" { "requiresauthentication" true }, "addfield" { "requiresauthentication" true } } ステップ7 back4appを使用したユーザープロファイルの構築 私たちのアプリケーションのユーザープロファイルは、カスタムフィールドを持つparseの組み込みユーザークラスを活用しています。profilepage jsがback4appとどのように相互作用するかを見てみましょう: back4appユーザークラスの拡張 parseユーザークラスは、私たちのソーシャルネットワークのために追加のフィールドで拡張されています: カスタムユーザーフィールド アバター (ファイル):back4appのファイルストレージに保存されたプロフィール写真 バイオ (文字列):ユーザーの経歴 ウェブサイト (文字列):ユーザーのウェブサイトurl 表示名 (文字列):ユーザーの表示名 ユーザーデータと投稿の取得 私たちのprofilepageは、ユーザーデータとユーザーの投稿の両方を取得します: // from profilepage js profile data fetching const fetchuserdata = async () => { try { // get current user from parse session const currentuser = await parse user current(); if (!currentuser) { navigate('/login'); return; } setuser(currentuser); // create a query to find posts by this user const post = parse object extend('post'); const query = new parse query(post); query equalto('author', currentuser); query include('author'); query descending('createdat'); const results = await query find(); // transform parse objects to plain objects const postslist = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setposts(postslist); setstats(prevstats => ({ prevstats, posts postslist length })); } catch (error) { console error('error fetching user data ', error); toaster create({ title 'error', description 'failed to load profile data', type 'error', }); } finally { setisloading(false); } }; back4appの主要なメカニズム: parse user current() セッショントークンから認証されたユーザーを取得します query equalto('author', currentuser) 現在のユーザーによる投稿を見つけるためのポインター等価制約を作成します post get('image') url() back4appに保存されたparseファイルオブジェクトのurlにアクセスします ユーザー設定の実装 設定ページでは、ユーザーがプロフィール情報を更新し、アカウント設定を管理できます。back4appとの相互作用を見てみましょう: // from settingspage js user settings implementation function settingspage() { const \[privacysettings, setprivacysettings] = usestate({ profilevisibility 'public', postprivacy 'friends' }); const \[twofactorauth, settwofactorauth] = usestate(false); const \[isopen, setisopen] = usestate(false); const cancelref = useref(); // save user settings to back4app const savesettings = async (settingstype, settingsdata) => { try { const currentuser = await parse user current(); if (!currentuser) { toaster create({ title 'error', description 'you must be logged in to save settings', type 'error', }); return; } // update the appropriate settings based on type switch (settingstype) { case 'privacy' currentuser set('privacysettings', settingsdata); break; case 'security' currentuser set('securitysettings', settingsdata); break; case 'notifications' currentuser set('notificationsettings', settingsdata); break; default break; } // save the user object await currentuser save(); toaster create({ title 'success', description 'your settings have been saved', type 'success', }); } catch (error) { toaster create({ title 'error', description error message, type 'error', }); } }; return ( \<box maxw="800px" mx="auto" p={4}> \<heading mb={6}>account settings\</heading> \<tabs root defaultvalue="profile"> \<tabs list> \<tabs trigger value="profile">profile\</tabs trigger> \<tabs trigger value="privacy">privacy\</tabs trigger> \<tabs trigger value="security">security\</tabs trigger> \<tabs trigger value="notifications">notifications\</tabs trigger> \<tabs indicator /> \</tabs list> {/ settings tabs content /} {/ /} \</tabs root> {/ account deactivation dialog /} \<dialog root open={isopen} onopenchange={setisopen}> {/ /} \</dialog root> \</box> ); } back4appの主要なメカニズム: parse user current() 現在のユーザーを取得して設定を更新します currentuser set() parse userオブジェクト内のユーザー属性を更新します currentuser save() 変更をback4appに永続化します back4appユーザー設定スキーマ back4appで設定を実装するには: これらのフィールドをuserクラスに追加します: privacysettings (オブジェクト):プライバシー設定を含むjsonオブジェクト securitysettings (オブジェクト):セキュリティ設定を含むjsonオブジェクト notificationsettings (オブジェクト):通知設定を含むjsonオブジェクト これらのオブジェクトの例のスキーマ: // privacysettings { "profilevisibility" "public", // または "friends" または "private" "postprivacy" "friends", // または "public" または "private" "showactivity" true } // securitysettings { "twofactorauth" false, "loginalerts" true } // notificationsettings { "likes" true, "comments" true, "follows" true, "messages" true } ソーシャルインタラクションのためのback4app cloud functions より複雑な社会的相互作用を実装するには、back4appでcloud functionsを実装できます。たとえば、コメント通知を追跡するために: // example cloud function for comment notifications parse cloud aftersave("comment", async (request) => { // only run for new comments, not updates if (request original) return; const comment = request object; const post = comment get("post"); const commenter = request user; // skip if user is commenting on their own post const postquery = new parse query("post"); const fullpost = await postquery get(post id, { usemasterkey true }); const postauthor = fullpost get("author"); if (postauthor id === commenter id) return; // create a notification const notification = parse object extend("notification"); const notification = new notification(); notification set("type", "comment"); notification set("fromuser", commenter); notification set("touser", postauthor); notification set("post", post); notification set("read", false); await notification save(null, { usemasterkey true }); }); これを実装するには: back4appのダッシュボードに移動する 「cloud code」>「cloud functions」に移動する 上記のコードで新しい関数を作成する 関数をデプロイする ステップ8 — リアルタイムメッセージングの構築 このステップでは、back4appのlivequery機能を使用してリアルタイムメッセージング機能を実装します。これにより、ユーザーはページをリフレッシュすることなく瞬時にメッセージを交換でき、人気のメッセージングプラットフォームに似たダイナミックなチャット体験を作成します。 back4app livequeryの理解 実装に入る前に、back4appのlivequeryがどのように機能するかを理解しましょう: livequeryとは何ですか? livequeryは、クライアントがクエリにサブスクライブできるparse serverの機能です これらのクエリに一致するオブジェクトが変更されると、サーバーは自動的に更新を購読しているクライアントにプッシュします これにより、複雑なwebsocket処理を自分で実装することなく、リアルタイム機能が作成されます ライブクエリの仕組み livequeryはクライアントとサーバーの間にwebsocket接続を確立します クライアントは監視したい特定のクエリにサブスクライブします これらのクエリに一致するデータが変更されると、サーバーはwebsocketを介してイベントを送信します クライアントはこれらのイベントを受信し、それに応じてuiを更新します ライブクエリエベント 作成する クエリに一致する新しいオブジェクトが作成されたときにトリガーされます 更新 クエリに一致する既存のオブジェクトが更新されたときにトリガーされます 入力 クエリに一致するオブジェクトが開始されたときにトリガーされます 離れる オブジェクトがクエリに一致しなくなったときにトリガーされます 削除 クエリに一致するオブジェクトが削除されたときにトリガーされます back4appでlivequeryを設定する アプリケーションのlivequeryを有効にするには、次の手順に従ってください: back4appサブドメインを有効にする back4appアカウントにログインする 「アプリ設定」>「サーバー設定」に移動します 「サーバーurlとライブクエリ」ブロックを見つけて、「設定」をクリックします 「back4appサブドメインを有効にする」オプションをチェックしてください このサブドメインはあなたのlivequeryサーバーとして機能します ライブクエリを有効にする 同じ設定ページで、「ライブクエリを有効にする」オプションをチェックしてください livequeryで監視したいクラスを選択してください: メッセージ(チャットメッセージ用) 入力状況(入力インジケーター用) 会話(会話の更新用) 変更を保存する ライブクエリサーバーのurlをメモしてください あなたのlivequeryサーバーのurlは次の形式になります wss\ //yourappname back4app io このurlは、reactアプリケーションでlivequeryクライアントを初期化するために必要です reactアプリでのlivequeryの設定 reactアプリケーションでlivequeryを使用するには、livequeryクライアントを初期化する必要があります: // from parseconfig js or app js parse initialize( process env react app parse app id, process env react app parse js key ); parse serverurl = process env react app parse server url; // initialize live queries with your subdomain parse livequeryserverurl = process env react app parse live query url; // e g , 'wss\ //yourappname back4app io' あなたの env local ファイルに、次の内容を含めることを確認してください react app parse live query url=wss\ //yourappname back4app io メッセージングのためのデータモデルの作成 私たちのメッセージングシステムは、back4appで2つの主要なクラスを必要とします: 会話クラス 参加者 (配列) 会話に参加しているユーザーのユーザーポインタの配列 最後のメッセージ (文字列) 最も最近のメッセージの内容 最終メッセージ日付 (日付) 最も最近のメッセージのタイムスタンプ 更新日時 (日付) parseによって自動的に管理されます メッセージクラス 会話 (ポインター) このメッセージが属する会話を指します 送信者 (ポインタ):メッセージを送信したユーザーを指します コンテンツ (文字列) メッセージのテキストコンテンツ 読む (ブール値) メッセージが読まれたかどうか 作成日時 (日付) parseによって自動的に管理されます タイピングステータスクラス 会話 (ポインター) 会話を指します ユーザー (ポインター) タイピングしているユーザーを指します 入力中 (ブール値) ユーザーが現在入力中かどうか メッセージングインターフェースの実装 私たちのmessagespageがリアルタイムメッセージングをどのように実装しているかを見てみましょう: // from messagespage js component structure function messagespage() { const \[conversations, setconversations] = usestate(\[]); const \[selectedconversation, setselectedconversation] = usestate(null); const \[messages, setmessages] = usestate(\[]); const \[newmessage, setnewmessage] = usestate(''); const \[isloading, setisloading] = usestate(true); const \[typingusers, settypingusers] = usestate(\[]); const messagesendref = useref(null); const messagesubscription = useref(null); const typingsubscription = useref(null); const conversationsubscription = useref(null); const typingtimeoutref = useref(null); // rest of the component } このコンポーネントは、いくつかの状態を維持します: conversations ユーザーの会話のリスト selectedconversation 現在選択されている会話 messages 選択された会話のメッセージ typingusers 現在会話中に入力中のユーザー また、livequeryのサブスクリプションを保存し、入力インジケーターを管理するためにrefsを使用します。 メッセージのためのlivequeryへのサブスクリプション リアルタイムメッセージングの鍵は、現在の会話のメッセージに対してlivequeryにサブスクライブすることです: // from messagespage js livequery subscription for messages const subscribetomessages = async (conversation) => { try { // unsubscribe from previous subscription if exists if (messagesubscription current) { messagesubscription current unsubscribe(); } // create a query for messages in this conversation const message = parse object extend('message'); const query = new parse query(message); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversation id; // find messages for this conversation query equalto('conversation', conversationpointer); // include the sender information query include('sender'); // subscribe to the query const subscription = await query subscribe(); messagesubscription current = subscription; // when a new message is created subscription on('create', (message) => { // add the new message to our state setmessages(prevmessages => { // check if message already exists in our list const exists = prevmessages some(m => m id === message id); if (exists) return prevmessages; // add the new message return \[ prevmessages, { id message id, content message get('content'), createdat message get('createdat'), sender { id message get('sender') id, username message get('sender') get('username'), avatar message get('sender') get('avatar') ? message get('sender') get('avatar') url() null }, read message get('read') }]; }); // scroll to bottom when new message arrives scrolltobottom(); // mark message as read if from other user if (message get('sender') id !== parse user current() id) { markmessageasread(message); } }); } catch (error) { console error('error subscribing to messages ', error); } }; 主要なlivequeryメカニズム: クエリの作成 現在の会話のメッセージのためのクエリを作成します クエリへのサブスクリプション query subscribe() を呼び出して変更をリッスンし始めます イベントの処理 subscription on('create', callback) を使用して新しいメッセージを処理します サブスクリプションの解除 サブスクリプションの参照を保存し、必要に応じて解除します livequeryを使用したタイピングインジケーターの実装 タイピングインジケーターは、livequeryを使用して実装された別のリアルタイム機能です: // from messagespage js livequery for typing indicators const subscribetotypingstatus = async (conversation) => { try { // unsubscribe from previous subscription if exists if (typingsubscription current) { typingsubscription current unsubscribe(); } // create a query for typing status in this conversation const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversation id; // find typing status for this conversation query equalto('conversation', conversationpointer); // include the user information query include('user'); // subscribe to the query const subscription = await query subscribe(); typingsubscription current = subscription; // when a typing status is updated subscription on('update', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // update typing users list settypingusers(prevtypingusers => { // if user is typing, add them to the list if (istyping) { // check if user is already in the list const exists = prevtypingusers some(u => u id === user id); if (exists) return prevtypingusers; // add user to typing list return \[ prevtypingusers, { id user id, username user get('username') }]; } else { // if user stopped typing, remove them from the list return prevtypingusers filter(u => u id !== user id); } }); }); // when a new typing status is created subscription on('create', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); // only add to typing users if they are actually typing if (istyping && user id !== parse user current() id) { settypingusers(prevtypingusers => { // check if user is already in the list const exists = prevtypingusers some(u => u id === user id); if (exists) return prevtypingusers; // add user to typing list return \[ prevtypingusers, { id user id, username user get('username') }]; }); } }); } catch (error) { console error('error subscribing to typing status ', error); } }; タイピングステータスの更新 ユーザーが入力すると、私たちは彼らのタイピングステータスを更新します: // from messagespage js updating typing status const updatetypingstatus = async (istyping) => { if (!selectedconversation) return; try { const currentuser = await parse user current(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = selectedconversation id; // check if a typing status already exists const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); query equalto('conversation', conversationpointer); query equalto('user', currentuser); let typingstatus = await query first(); if (typingstatus) { // update existing typing status typingstatus set('istyping', istyping); } else { // create new typing status typingstatus = new typingstatus(); typingstatus set('conversation', conversationpointer); typingstatus set('user', currentuser); typingstatus set('istyping', istyping); } await typingstatus save(); } catch (error) { console error('error updating typing status ', error); } }; // handle typing indicator with debounce const handletyping = () => { updatetypingstatus(true); // clear previous timeout if (typingtimeoutref current) { cleartimeout(typingtimeoutref current); } // set typing to false after 3 seconds of inactivity typingtimeoutref current = settimeout(() => { updatetypingstatus(false); }, 3000); }; メッセージの送信 メッセージを送信する際には、新しいメッセージオブジェクトを作成し、livequeryに更新を処理させます // from messagespage js sending messages const sendmessage = async (e) => { e preventdefault(); if (!newmessage trim() || !selectedconversation) return; try { const currentuser = await parse user current(); // create a pointer to the conversation const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = selectedconversation id; // create a new message const message = parse object extend('message'); const message = new message(); message set('content', newmessage); message set('sender', currentuser); message set('conversation', conversationpointer); message set('read', false); await message save(); // update the conversation with the last message const conversation = await new parse query(conversation) get(selectedconversation id); conversation set('lastmessage', newmessage); conversation set('lastmessagedate', new date()); await conversation save(); // clear the input setnewmessage(''); // set typing status to false updatetypingstatus(false); } catch (error) { console error('error sending message ', error); toast({ title 'error', description 'failed to send message', status 'error', duration 3000, isclosable true, }); } }; livequeryサブスクリプションのクリーンアップ もはや必要ないときはlivequeryサブスクリプションをクリーンアップすることが重要です: // from messagespage js cleanup useeffect(() => { // fetch initial conversations fetchconversations(); subscribetoconversations(); // cleanup subscriptions when component unmounts return () => { if (messagesubscription current) { messagesubscription current unsubscribe(); } if (typingsubscription current) { typingsubscription current unsubscribe(); } if (conversationsubscription current) { conversationsubscription current unsubscribe(); } if (typingtimeoutref current) { cleartimeout(typingtimeoutref current); } }; }, \[]); back4app livequeryのパフォーマンス考慮事項 livequeryを実装する際は、これらのパフォーマンステクニックを考慮してください: クエリを具体的にする 必要なデータのみにサブスクライブする 制約を使用してサブスクリプションの範囲を制限する 例えば、現在の会話のメッセージのみにサブスクライブする サブスクリプションを慎重に管理する データが不要になったらサブスクリプションを解除する コンテキストが変わったときに新しいサブスクリプションを作成する 後でサブスクリプションを解除するために参照を保存する セキュリティのためにaclを使用する メッセージと会話オブジェクトに適切なaclを設定する ユーザーが参加している会話のみアクセスできるようにする livequeryはaclを尊重するため、権限のないユーザーは更新を受け取れない livequeryサーバーを最適化する back4appダッシュボードで、livequeryが必要なクラスを設定する リアルタイム更新が不要なクラスにはlivequeryを有効にしない ステップ9 — 検索機能の実装 このステップでは、私たちのソーシャルネットワークのために包括的な検索機能を実装します。ユーザーは他のユーザー、コンテンツによる投稿、ハッシュタグを検索できるようになります。この機能により、ユーザーはコンテンツを発見し、プラットフォーム上で他の人とつながることが容易になります。 back4appにおける検索の理解 実装に入る前に、back4appでの検索の仕組みを理解しましょう: パースクエリシステム back4appは、検索のためにparse serverのクエリシステムを使用します クエリは複数のクラスにまたがって実行できます 正確な一致、含む、始まるなどで検索できます テキスト検索オプション startswith 特定の文字列で始まる文字列を見つけます contains 特定の部分文字列を含む文字列を見つけます matches より複雑なパターンマッチングのために正規表現を使用します fulltext (エンタープライズ機能) 高度な全文検索機能を提供します パフォーマンスの考慮事項 テキスト検索はリソースを多く消費する可能性があります 頻繁に検索されるフィールドにはインデックスを作成する必要があります クエリは結果の数を制限するように最適化する必要があります 検索ページの構築 私たちのsearchpageコンポーネントは、さまざまなタイプの検索を処理し、結果を表示します。その構造を見てみましょう: // from searchpage js component structure function searchpage() { const \[searchquery, setsearchquery] = usestate(''); const \[searchtype, setsearchtype] = usestate('users'); // 'users', 'posts', 'hashtags' const \[searchresults, setsearchresults] = usestate(\[]); const \[isloading, setisloading] = usestate(false); const \[trendingtopics, settrendingtopics] = usestate(\[]); // rest of the component } コンポーネントは次の状態を維持します: ユーザーが入力した検索クエリ 実行されている検索のタイプ 検索結果 読み込み状態 トレンドのトピック ユーザー検索の実装 back4appでユーザーを検索する方法を見てみましょう: // from searchpage js user search implementation const searchusers = async (query) => { setisloading(true); try { // create a query on the user class const userquery = new parse query(parse user); // search for usernames that contain the query string (case insensitive) userquery matches('username', new regexp(query, 'i')); // limit results to improve performance userquery limit(20); const users = await userquery find(); // transform parse objects to plain objects const userresults = users map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null, bio user get('bio') || '' })); setsearchresults(userresults); } catch (error) { console error('error searching users ', error); toaster create({ title 'error', description 'failed to search users', type 'error', }); } finally { setisloading(false); } }; back4appの主要なメカニズム: new parse query(parse user) ユーザークラスに対するクエリを作成します userquery matches('username', new regexp(query, 'i')) ユーザー名に対して大文字小文字を区別しない正規表現マッチを実行します userquery limit(20) パフォーマンスを向上させるために結果を制限します userquery find() クエリを実行し、一致するユーザーを返します 投稿コンテンツ検索の実装 次に、コンテンツによる投稿の検索方法を見てみましょう: // from searchpage js post search implementation const searchposts = async (query) => { setisloading(true); try { // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the query string postquery matches('content', new regexp(query, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(20); const posts = await postquery find(); // transform parse objects to plain objects const postresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setsearchresults(postresults); } catch (error) { console error('error searching posts ', error); toaster create({ title 'error', description 'failed to search posts', type 'error', }); } finally { setisloading(false); } }; back4appの主要なメカニズム: parse object extend('post') postクラスを参照します postquery matches('content', new regexp(query, 'i')) 投稿内容に対して大文字小文字を区別しない正規表現マッチを実行します postquery include('author') 単一のクエリで著者情報を含めます postquery descending('createdat') 作成日で結果をソートします ハッシュタグ検索の実装 ハッシュタグ検索には異なるアプローチが必要です。ハッシュタグを含む投稿を検索します: // from searchpage js hashtag search implementation const searchhashtags = async (query) => { setisloading(true); try { // remove # if present at the beginning const hashtagquery = query startswith('#') ? query substring(1) query; // create a query on the post class const post = parse object extend('post'); const postquery = new parse query(post); // search for posts with content containing the hashtag // we use word boundaries to find actual hashtags postquery matches('content', new regexp(`#${hashtagquery}\\\b`, 'i')); // include the author information postquery include('author'); // sort by creation date (newest first) postquery descending('createdat'); // limit results postquery limit(20); const posts = await postquery find(); // transform parse objects to plain objects const hashtagresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); setsearchresults(hashtagresults); } catch (error) { console error('error searching hashtags ', error); toaster create({ title 'error', description 'failed to search hashtags', type 'error', }); } finally { setisloading(false); } }; back4appの主要なメカニズム: 単語境界を持つ正規表現を使用します( \\\b )を使用して実際のハッシュタグを見つけます このアプローチは、コンテンツに特定のハッシュタグが含まれている投稿を見つけます トレンドトピックの実装 トレンドトピックを実装するには、最近の投稿を分析し、ハッシュタグの出現回数をカウントする必要があります: // from searchpage js fetching trending topics const fetchtrendingtopics = async () => { try { // create a query on the post class const post = parse object extend('post'); const query = new parse query(post); // get posts from the last 7 days const oneweekago = new date(); oneweekago setdate(oneweekago getdate() 7); query greaterthan('createdat', oneweekago); // limit to a reasonable number for analysis query limit(500); const posts = await query find(); // extract hashtags from post content const hashtagcounts = {}; posts foreach(post => { const content = post get('content') || ''; // find all hashtags in the content const hashtags = content match(/#(\w+)/g) || \[]; // count occurrences of each hashtag hashtags foreach(hashtag => { const tag = hashtag tolowercase(); hashtagcounts\[tag] = (hashtagcounts\[tag] || 0) + 1; }); }); // convert to array and sort by count const trendingarray = object entries(hashtagcounts) map((\[hashtag, count]) => ({ hashtag, count })) sort((a, b) => b count a count) slice(0, 10); // get top 10 settrendingtopics(trendingarray); } catch (error) { console error('error fetching trending topics ', error); } }; back4appの主要なメカニズム: 私たちは、過去7日間の投稿をクエリします query greaterthan('createdat', oneweekago) 私たちは、コンテンツを分析してハッシュタグを抽出し、カウントします 私たちは、頻度でソートして最も人気のあるハッシュタグを見つけます 検索実行の処理 では、検索タイプに基づいて検索実行をどのように処理するか見てみましょう: // from searchpage js search execution const handlesearch = (e) => { e preventdefault(); if (!searchquery trim()) return; switch (searchtype) { case 'users' searchusers(searchquery); break; case 'posts' searchposts(searchquery); break; case 'hashtags' searchhashtags(searchquery); break; default searchusers(searchquery); } }; back4appでの検索の最適化 back4appでの検索パフォーマンスを最適化するには: インデックスを作成する back4appダッシュボードに移動する 「データベースブラウザ」に移動 > クラスを選択する(例:ユーザー、投稿) 「インデックス」タブをクリックする 頻繁に検索されるフィールドのインデックスを作成する: ユーザークラスの場合: username 投稿クラスの場合: content クエリ制約を使用する 常に limit() を使用して結果の数を制限する 使用する select() 必要なフィールドのみを取得する 使用する skip() 大きな結果セットを扱う際のページネーションに 複雑な検索のためにクラウド関数を考慮する より複雑な検索ロジックの場合、クラウド関数を実装する これにより、サーバー側で処理を行い、最適化された結果を返すことができます 高度な検索のための例のクラウド関数: // example cloud function for advanced search parse cloud define("advancedsearch", async (request) => { const { query, type, limit = 20 } = request params; if (!query) { throw new error("search query is required"); } let results = \[]; switch (type) { case 'users' const userquery = new parse query(parse user); userquery matches('username', new regexp(query, 'i')); userquery limit(limit); results = await userquery find({ usemasterkey true }); break; case 'posts' const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(query, 'i')); postquery include('author'); postquery limit(limit); results = await postquery find({ usemasterkey true }); break; // add more search types as needed default throw new error("invalid search type"); } return results; }); ステップ 10 — ソーシャルネットワークのテストとデプロイ この最終ステップでは、アプリケーションのテスト方法、プロダクション用の準備、ホスティングサービスへのデプロイ、back4appバックエンドの監視とスケーリングについて説明します。これらのステップは、ソーシャルネットワークがプロダクション環境でスムーズに動作することを保証するために重要です。 アプリケーションのテスト デプロイする前に、バグや問題を見つけるためにアプリケーションを徹底的にテストすることが重要です: 1 手動テスト アプリケーションのすべての主要機能をカバーするテスト計画を作成します: ユーザー認証 有効および無効な入力によるテスト登録 正しいおよび不正な資格情報でのテストログイン パスワードリセット機能のテスト テストセッションの持続性とログアウト 投稿機能 テキストと画像を使って投稿を作成するテスト フィードでの投稿の表示テスト 投稿へのいいねとコメントのテスト 投稿の削除テスト 社会的相互作用 ユーザープロフィールのテスト表示 投稿にコメントするテスト リアルタイムメッセージングのテスト 検索機能 ユーザーの検索テスト 投稿の検索テスト ハッシュタグ検索のテスト クロスブラウザテスト chrome、firefox、safari、edgeでのテスト モバイルブラウザでのテスト 2 自動テスト より堅牢なテストのために、自動テストを実装します: // example jest test for the login component import react from 'react'; import { render, fireevent, waitfor } from '@testing library/react'; import loginpage from ' /src/pages/loginpage'; import parse from 'parse/dist/parse min js'; // mock parse jest mock('parse/dist/parse min js', () => ({ user { login jest fn() } })); test('login form submits with username and password', async () => { parse user login mockresolvedvalueonce({ id '123', get () => 'testuser' }); const { getbylabeltext, getbyrole } = render(\<loginpage />); // fill in the form fireevent change(getbylabeltext(/username/i), { target { value 'testuser' } }); fireevent change(getbylabeltext(/password/i), { target { value 'password123' } }); // submit the form fireevent click(getbyrole('button', { name /log in/i })); // check if parse user login was called with correct arguments await waitfor(() => { expect(parse user login) tohavebeencalledwith('testuser', 'password123'); }); }); 3 back4app テスト back4appの設定をテストします: クラウドファンクション 様々な入力で全てのクラウドファンクションをテストします セキュリティ クラスレベルの権限が正しく機能していることを確認します livequery 複数のクライアントでリアルタイム機能をテストします 本番環境の準備 デプロイする前に、アプリケーションを本番環境用に最適化します: 1 環境設定 開発用と本番用の別々の環境ファイルを作成します: # env development react app parse app id=your dev app id react app parse js key=your dev js key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your dev app back4app io \# env production react app parse app id=your production app id react app parse js key=your production js key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your prod app back4app io 2 ビルド最適化 reactビルドを最適化します: // in your package json "scripts" { "analyze" "source map explorer 'build/static/js/ js'", "build" "generate sourcemap=false react scripts build" } バンドルサイズを分析するためにsource map explorerをインストールします: npm install save dev source map explorer 3 パフォーマンス最適化 初期ロード時間を短縮するためにコード分割を実装します: // in app js, use react lazy for route components import react, { suspense, lazy } from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider } from '@chakra ui/react'; import loadingspinner from ' /components/loadingspinner'; // lazy load pages const landingpage = lazy(() => import(' /pages/landingpage')); const loginpage = lazy(() => import(' /pages/loginpage')); const feedpage = lazy(() => import(' /pages/feedpage')); // other pages function app() { return ( \<chakraprovider> \<router> \<suspense fallback={\<loadingspinner />}> \<routes> \<route path="/" element={\<landingpage />} /> \<route path="/login" element={\<loginpage />} /> \<route path="/feed" element={\<feedpage />} /> {/ other routes /} \</routes> \</suspense> \</router> \</chakraprovider> ); } ホスティングサービスへのデプロイ reactアプリケーションをデプロイするためのいくつかのオプションがあります: 1 vercelへのデプロイ vercelはreactアプリケーションにとって素晴らしい選択肢です: vercel cliをインストールします: npm install g vercel アプリケーションをデプロイします: vercel 本番環境にデプロイするには: vercel prod 2 netlifyへのデプロイ netlifyはもう一つの優れた選択肢です: netlify cliをインストールします: npm install g netlify cli アプリケーションをビルドします: npm run build netlifyにデプロイします: netlify deploy 本番環境にデプロイするには: netlify deploy prod 3 github pagesへのデプロイ シンプルなデプロイメントオプションのために: gh pagesをインストール: npm install save dev gh pages package jsonに追加: "homepage" "https //yourusername github io/your repo name", "scripts" { "predeploy" "npm run build", "deploy" "gh pages d build" } デプロイ: npm run deploy back4appバックエンドの監視とスケーリング ソーシャルネットワークが成長するにつれて、back4appバックエンドを監視し、スケールする必要があります: 1 パフォーマンスの監視 back4appは、アプリケーションを監視するためのいくつかのツールを提供します: ダッシュボード分析 apiリクエスト、ストレージ使用量、ファイル操作を監視する ログ エラーやパフォーマンスの問題についてサーバーログを確認する パフォーマンス指標 応答時間を追跡し、ボトルネックを特定する これらのツールにアクセスするには: back4appダッシュボードに移動する 使用統計のために「分析」に移動する 詳細な操作ログのために「ログ」を確認する 2 バックエンドのスケーリング ユーザーベースが増加すると、back4appバックエンドをスケールする必要があるかもしれません: プランをアップグレードする より多くのリソースを持つ上位プランに移行する クエリを最適化する パフォーマンスを向上させるためにインデックスを使用し、クエリを制限する キャッシングを実装する 頻繁にアクセスされるデータのためにクライアント側キャッシングを使用する 3 データベースの最適化 パフォーマンス向上のためにデータベースを最適化します: インデックスを作成する よくクエリされるフィールドにインデックスを追加します // 例:userクラスの'username'フィールドにインデックスを作成する const schema = new parse schema(' user'); schema addindex('username index', { username 1 }); schema update(); 集約パイプラインを使用する 複雑なデータ操作のために // 例:ユーザーによる投稿のカウント const pipeline = \[ { group { objectid '$author', count { $sum 1 } } } ]; const results = await parse cloud aggregate('post', pipeline); 4 メディアのためのcdnの実装 画像やメディアの配信を速くするために: cloudflareやamazon cloudfrontのようなcdnを設定する cdnを使用するようにback4appのファイルストレージ設定を更新する アプリケーション内のファイルurlをcdnドメインを使用するように更新する 5 モニタリングアラートの設定 問題を通知するためのアラートを設定します: back4appダッシュボードに移動します 「アプリ設定」>「アラート」に移動します 次のためにアラートを設定します: 高いapi使用量 エラーレートの急増 データベースサイズの制限 サーバーダウンタイム 達成したことの概要 このチュートリアルを通じて、あなたは: 堅牢なバックエンドインフラを構築する back4appアカウントを作成し、アプリケーションを設定しました ユーザー、投稿、コメント、およびメッセージのためのデータベーススキーマを設計しました 構成されたセキュリティ設定とクラスレベルの権限 リアルタイム機能のためにlivequeryを設定する モダンなreactフロントエンドを構築しました chakra uiコンポーネントを使用してレスポンシブuiを作成しました react routerを使用してクライアントサイドのルーティングを実装しました 投稿、コメント、ユーザープロフィールの再利用可能なコンポーネントを開発しました parse javascript sdkを使用して、フロントエンドをback4appに接続しました コアソーシャルネットワーク機能を実装しました ユーザー認証(サインアップ、ログイン、パスワードリセット) 投稿の作成とインタラクション(いいね、コメント) ユーザープロフィールと設定 ユーザー間のリアルタイムメッセージング ユーザー、投稿、ハッシュタグの検索機能 生産向けに最適化 コード分割のようなパフォーマンス最適化を実装しました 開発と本番のための環境設定を行う アプリケーションをホスティングサービスにデプロイする方法を学びました back4appバックエンドの監視とスケーリング戦略を探求しました あなたは、特定のニーズに合わせて拡張およびカスタマイズできるソーシャルネットワークアプリケーションの堅実な基盤を持っています。 アプリケーションを拡張するための次のステップ ソーシャルネットワークアプリケーションを強化するためのいくつかのエキサイティングな方法をご紹介します: 高度なメディア機能 動画のアップロードと再生のサポートを追加する 画像フィルターと編集ツールを実装する ストーリーや一時的なコンテンツ機能を作成する gifやその他のリッチメディアのサポートを追加する 強化された社会的相互作用 友達提案のための推薦エンジンを実装する グループまたはコミュニティ機能を追加する rsvp機能を持つイベントを作成する すべてのユーザーインタラクションのための通知システムを開発する マネタイズオプション プレミアムメンバーシップ機能を実装する デジタル商品にアプリ内購入を追加する ユーザー間取引のためのマーケットプレイスを作成する stripeのような決済処理業者と統合する モバイル体験 アプリケーションをプログレッシブウェブアプリ(pwa)に変換する react nativeを使用してネイティブモバイルアプリを開発する モバイルデバイスのプッシュ通知を実装する 異なる画面サイズと向きに合わせてuiを最適化する 分析と洞察 ユーザーエンゲージメントを追跡するための分析ツールを統合する コンテンツパフォーマンスのためのダッシュボードを作成する 新機能のa/bテストを実施する プラットフォームを改善するためのユーザー行動の洞察を開発する コンテンツモデレーション 自動コンテンツフィルタリングを実装する 不適切なコンテンツのための報告システムを作成する コンテンツモデレーションのための管理ツールを開発する インテリジェントなコンテンツ分析のために機械学習を使用する 学習のための追加リソース 知識とスキルをさらに拡張するために、ここにいくつかの貴重なリソースがあります: 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 リアクトとモダンjavascript react ドキュメント https //reactjs org/docs/getting started html javascript info https //javascript info/ egghead ioのreactコース https //egghead io/q/react uiおよびuxデザイン chakra ui ドキュメント https //chakra ui com/docs/getting started uiデザインパターン https //ui patterns com/ ニールセン・ノーマン・グループ uxリサーチ https //www nngroup com/articles/ パフォーマンス最適化 web dev パフォーマンス https //web dev/performance scoring/ reactパフォーマンス最適化 https //reactjs org/docs/optimizing performance html google pagespeed insights https //developers google com/speed/pagespeed/insights/ コミュニティとサポート スタック・オーバーフロー https //stackoverflow\ com/questions/tagged/parse platform parseコミュニティフォーラム https //community parseplatform org/ react開発コミュニティ https //dev to/t/react 成功したソーシャルネットワークを構築することは反復的なプロセスであることを忘れないでください。しっかりとした基盤(今持っているもの)から始め、ユーザーのフィードバックを集め、実際の使用パターンに基づいてアプリケーションを継続的に改善してください。 このチュートリアルが、reactとback4appを使って素晴らしいアプリケーションを構築するための知識と自信を提供できたことを願っています。コーディングを楽しんでください