Project Templates
Social Network
Cómo construir una red social con React
122 min
introducción en este tutorial, construirás back4gram, una plataforma de red social completa similar a instagram back4gram permite a los usuarios crear cuentas, compartir publicaciones con imágenes, interactuar a través de 'me gusta' y comentarios, buscar contenido y comunicarse a través de mensajería en tiempo real este proyecto demuestra cómo combinar las potentes capacidades de frontend de react con los robustos servicios de backend de back4app para crear una aplicación social moderna y rica en características react es la elección perfecta para el frontend de una red social debido a su arquitectura basada en componentes, que permite elementos de ui reutilizables y un renderizado eficiente mientras tanto, back4app proporciona un backend de parse server gestionado que maneja la autenticación de usuarios, el almacenamiento de datos, las cargas de archivos y las características en tiempo real sin requerir que construyas una infraestructura de servidor compleja desde cero al completar este tutorial, construirás una red social completa con autenticación de usuario (registro, inicio de sesión, restablecimiento de contraseña) gestión de perfil creación de publicaciones con carga de imágenes interacciones sociales (me gusta, comentarios) mensajería en tiempo real con indicadores de escritura funcionalidad de búsqueda de contenido configuraciones y preferencias del usuario a lo largo del camino, adquirirás habilidades valiosas en desarrollo en react con hooks y contexto desarrollo de ui con chakra ui integración de parse server a través de back4app gestión de datos en tiempo real con livequery flujos de autenticación de usuario manejo de carga de archivos implementación de diseño responsivo ya sea que estés buscando lanzar tu propia plataforma social o simplemente quieras entender cómo se construyen las redes sociales modernas, este tutorial te proporcionará el conocimiento y la experiencia práctica para lograr tus objetivos en cualquier momento puedes acceder al código completo en github requisitos previos para completar este tutorial, necesitarás una cuenta de back4app regístrate para obtener una cuenta gratuita en back4app com https //www back4app com/ usarás esto para crear y gestionar tus servicios de backend node js y npm instalados en tu máquina local instalar node js (versión 14 x o posterior) y npm desde nodejs org https //nodejs org/ verifica tu instalación ejecutando node v y npm v en tu terminal comprensión básica de react familiaridad con los componentes de react, hooks y jsx si necesitas repasar react, consulta la documentación oficial de react https //reactjs org/docs/getting started html editor de código cualquier editor de código moderno como visual studio code, sublime text o atom git (opcional) para el control de versiones y seguir el repositorio recursos suplementarios documentación de back4app https //www back4app com/docs/get started/welcome guía de javascript de parse https //docs parseplatform org/js/guide/ documentación de chakra ui https //chakra ui com/docs/getting started documentación de react router https //reactrouter com/en/main paso 1 — configuración de su backend de back4app en este paso, crearás un nuevo proyecto de back4app y configurarás el esquema de base de datos necesario para tu aplicación de red social back4app proporciona un servidor parse administrado que manejará la autenticación de usuarios, el almacenamiento de datos y las características en tiempo real creando un nuevo proyecto de back4app inicia sesión en tu cuenta de back4app y navega al panel de control haz clic en el botón "crear una nueva aplicación" ingresa "back4gram" como el nombre de tu aplicación, selecciona la región del servidor más cercana y haz clic en "crear" una vez que tu aplicación esté creada, serás redirigido al panel de control de la aplicación entendiendo el esquema de la base de datos antes de crear clases en tu base de datos, entendamos el modelo de datos necesario para nuestra red social basado en los requisitos de nuestra aplicación, necesitaremos las siguientes clases usuario (ya existe por defecto en parse) esta clase maneja la autenticación de usuarios y la información del perfil lo extenderemos con campos adicionales como biografía y avatar publicar almacena publicaciones de usuarios que incluyen contenido de texto e imágenes campos contenido (cadena), autor (puntero a usuario), imagen (archivo), me gusta (número), comentarios (array), creado en (fecha) comentario almacena comentarios en publicaciones campos contenido (cadena), autor (puntero a usuario), publicación (puntero a publicación), creadoen (fecha) conversación representa una conversación de chat entre usuarios campos participantes (array de punteros a usuario), últimomensaje (cadena), actualizadoen (fecha) mensaje mensajes individuales dentro de una conversación campos texto (cadena), remitente (puntero a usuario), conversación (puntero a conversación), creadoen (fecha) estado de escritura (para indicadores de escritura en tiempo real) rastrea cuando los usuarios están escribiendo en una conversación campos usuario (puntero a usuario), conversación (puntero a conversación), estáescribiendo (booleano) creando clases de base de datos ahora, vamos a crear estas clases en tu base de datos de back4app navega a la sección "base de datos" en tu panel de control de back4app extendiendo la clase usuario haz clic en la clase "usuario" que ya existe agrega las siguientes columnas biografía (tipo cadena) avatar (tipo archivo) seguidores (tipo número, predeterminado 0) siguiente (tipo número, predeterminado 0) creando la clase post haz clic en "crear una clase" ingrese "post" como el nombre de la clase y seleccione "crear una clase vacía" agrega las siguientes columnas contenido (tipo cadena) autor (tipo puntero a usuario) imagen (tipo archivo) me gusta (tipo número, predeterminado 0) comentarios (tipo array) creadoen (tipo fecha, añadido automáticamente) creando la clase comentario haz clic en "crear una clase" ingrese "comentario" como el nombre de la clase y seleccione "crear una clase vacía" agrega las siguientes columnas contenido (tipo cadena) autor (tipo puntero a usuario) publicación (tipo puntero a publicación) creadoen (tipo fecha, añadido automáticamente) creando la clase conversación haz clic en "crear una clase" ingrese "conversación" como el nombre de la clase y seleccione "crear una clase vacía" agrega las siguientes columnas participantes (tipo array) últimomensaje (tipo cadena) actualizadoen (tipo fecha, añadido automáticamente) creando la clase message haz clic en "crear una clase" ingrese "mensaje" como el nombre de la clase y seleccione "crear una clase vacía" agrega las siguientes columnas texto (tipo cadena) remitente (tipo puntero a usuario) conversación (tipo puntero a conversación) creadoen (tipo fecha, añadido automáticamente) creando la clase typingstatus haz clic en "crear una clase" ingrese "typingstatus" como el nombre de la clase y seleccione "crear una clase vacía" agrega las siguientes columnas usuario (tipo puntero a usuario) conversación (tipo puntero a conversación) estáescribiendo (tipo booleano) configuración de permisos de clase (opcional) para garantizar la seguridad de los datos, necesitamos configurar listas de control de acceso (acl) apropiadas para cada clase navega a la sección "seguridad y claves" en tu panel de control de back4app bajo "seguridad de nivel de clase", configura los siguientes permisos clase de usuario acceso de lectura pública habilitado (para que los usuarios puedan ver los perfiles de otros usuarios) acceso de escritura público desactivado (los usuarios solo pueden modificar sus propios perfiles) clase de publicación acceso de lectura pública habilitado (todos pueden ver las publicaciones) acceso de escritura público habilitado (los usuarios autenticados pueden crear publicaciones) agregar un clp para actualizar/eliminar para restringir solo al autor comentario de clase acceso de lectura pública habilitado (todos pueden ver los comentarios) acceso de escritura público habilitado (los usuarios autenticados pueden crear comentarios) agregar un clp para actualizar/eliminar para restringir solo al autor clase de conversación acceso de lectura pública desactivado (las conversaciones son privadas) acceso de escritura público habilitado (los usuarios autenticados pueden crear conversaciones) agrega un clp para restringir el acceso de lectura/escritura a los participantes de la conversación clase de mensaje acceso de lectura pública desactivado (los mensajes son privados) acceso de escritura público habilitado (los usuarios autenticados pueden enviar mensajes) agrega un clp para restringir el acceso de lectura/escritura a los participantes de la conversación clase typingstatus acceso de lectura pública desactivado (el estado de escritura es privado) acceso de escritura público habilitado (los usuarios autenticados pueden actualizar el estado de escritura) agrega un clp para restringir el acceso de lectura/escritura a los participantes de la conversación configuración de livequery para características en tiempo real para habilitar funciones en tiempo real como mensajería e indicadores de escritura, necesitamos configurar livequery navega a la sección "configuración del servidor" en tu panel de back4app bajo "parse server", encuentra la sección "livequery" y habilítala agrega las siguientes clases para ser monitoreadas por livequery mensaje estadodeescritura publicación (para actualizaciones en tiempo real de me gusta y comentarios) guarda tus cambios obteniendo tus claves de aplicación necesitarás tus claves de aplicación de back4app para conectar tu frontend de react al backend navega a la sección "configuración de la aplicación" > "seguridad y claves" anota las siguientes claves id de la aplicación clave de javascript url del servidor url del servidor livequery (configuración de subdominio, para características en tiempo real) usarás estas claves en tu aplicación de react para inicializar parse paso 2 — creando un proyecto frontend de react en este paso, configurarás un nuevo proyecto de react y lo configurarás para trabajar con tu backend de back4app instalarás las dependencias necesarias, crearás la estructura del proyecto y te conectarás a tu servidor parse configurando un nuevo proyecto de react comencemos creando una nueva aplicación de react usando create react app, que proporciona una configuración de compilación moderna sin necesidad de configuración abre tu terminal y navega al directorio donde deseas crear tu proyecto ejecuta el siguiente comando para crear una nueva aplicación de react npx create react app back4gram una vez que se crea el proyecto, navega al directorio del proyecto cd back4gram inicia el servidor de desarrollo para asegurarte de que todo esté funcionando npm start esto abrirá tu nueva aplicación de react en tu navegador en http //localhost 3000 http //localhost 3000 instalando dependencias necesarias ahora, instalemos los paquetes que necesitaremos para nuestra aplicación de red social detén el servidor de desarrollo (presiona ctrl+c en tu terminal) instala el sdk de parse para conectarte a back4app npm install parse instala react router para la navegación npm install react router dom instala chakra ui para nuestros componentes de interfaz de usuario npm install @chakra ui/react @emotion/react @emotion/styled framer motion instala utilidades adicionales de ui y bibliotecas de íconos npm install react icons explicación de la estructura del proyecto organizaremos nuestro proyecto con una estructura clara crea los siguientes directorios en el src carpeta mkdir p src/components/ui src/pages src/contexts src/utils aquí está para qué sirve cada directorio componentes componentes de ui reutilizables ui componentes básicos de ui como botones, formularios, modales otras carpetas de componentes para características específicas (por ejemplo, publicaciones, comentarios) páginas componentes de página completa que corresponden a rutas contextos proveedores de contexto de react para la gestión del estado utilidades funciones utilitarias y ayudantes creando variables de entorno para almacenar de forma segura tus credenciales de back4app, crea un env archivo en la raíz de tu proyecto crea un nuevo archivo llamado env local en la raíz del proyecto touch env local abre el archivo y agrega tus credenciales de back4app react app parse app id=tu id de aplicación react app parse js key=tu clave de javascript react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //tu id de aplicación back4app io reemplaza los valores de marcador de posición con tus credenciales reales de back4app del paso 1 asegúrate de agregar env local a tu gitignore archivo para evitar comprometer información sensible configurando el sdk de parse con credenciales de back4app ahora, configuremos el sdk de parse para conectarnos a su backend de back4app cree un nuevo archivo src/utils/parseconfig js // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // inicializar 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; // inicializar live queries if (process env react app parse live query url) { parse livequeryserverurl = process env react app parse live query url; } export default parse; actualice su src/index js archivo para importar la configuración de 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'; // importar configuración de parse const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); reportwebvitals(); configurando el componente de la aplicación con enrutamiento actualicemos el componente principal de la aplicación para incluir enrutamiento y el proveedor de chakra ui actualizar src/app js import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import { chakraprovider, extendtheme } from '@chakra ui/react'; // importar páginas (crearemos estas a continuación) 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'; // crear un tema personalizado 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; creando un componente de ruta protegida para asegurar rutas que requieren autenticación, vamos a crear un componente protectedroute primero, crea un authcontext para gestionar el estado de autenticación del usuario // 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>; } ahora, crea el componente 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; actualiza el componente app para usar authprovider y protectedroute // 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; creando una página de aterrizaje básica vamos a crear una página de aterrizaje simple para probar nuestra configuración // 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; probando tu configuración ahora que has configurado la estructura básica de tu aplicación react y la has conectado a back4app, probémosla inicia el servidor de desarrollo npm start abre tu navegador y navega a http //localhost 3000 http //localhost 3000 deberías ver la página de inicio con botones para registrarte o iniciar sesión revisa la consola de tu navegador para asegurarte de que no hay errores relacionados con la inicialización de parse paso 3 — implementando características de autenticación en este paso, implementaremos características de autenticación de usuarios para nuestra aplicación de red social utilizando el servidor parse de back4app examinaremos cómo funciona el sistema de autenticación de parse e implementaremos la funcionalidad de inicio de sesión, registro y restablecimiento de contraseña entendiendo el sistema de autenticación de usuarios de parse el servidor parse de back4app proporciona un sistema integral de gestión de usuarios a través de la parse user clase entendamos cómo funciona la autenticación de parse en nuestra aplicación la clase parse user la parse user clase es una subclase especial de parse object diseñada específicamente para la gestión de usuarios en nuestra aplicación back4gram, la usamos para almacenar credenciales de usuario (nombre de usuario, correo electrónico, contraseña) gestionar el estado de autenticación manejar tokens de sesión automáticamente al observar nuestra implementación, podemos ver cómo interactuamos con la clase 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(); este código crea un nuevo objeto parse user, establece los campos requeridos y llama al método signup() para registrar al usuario en back4app flujo de autenticación en parse examinemos cómo funciona la autenticación en nuestra aplicación flujo de registro en nuestro signuppage js, recopilamos nombre de usuario, correo electrónico y contraseña validamos los datos de entrada (verificando campos vacíos, formato de correo electrónico válido, longitud de la contraseña) creamos un nuevo objeto parse user y establecemos las credenciales llamamos a signup() que envía los datos a back4app parse hashea la contraseña antes de almacenarla al tener éxito, el usuario inicia sesión automáticamente con un token de sesión flujo de inicio de sesión en nuestro loginpage js, recopilamos el nombre de usuario y la contraseña llamamos a parse user login() con estas credenciales parse verifica las credenciales contra los datos almacenados si es válido, parse genera un token de sesión el token de sesión se almacena automáticamente en el almacenamiento del navegador gestión de sesiones parse incluye automáticamente el token de sesión en todas las solicitudes de api usamos parse user current() para recuperar el usuario que ha iniciado sesión actualmente las sesiones persisten a través de las actualizaciones de página implementación del registro de usuarios examinemos nuestro componente signuppage para entender cómo se implementa el registro de usuarios validación de formularios antes de enviar datos a back4app, validamos la entrada del usuario // 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; }; esta validación asegura que el nombre de usuario no está vacío el correo electrónico es válido la contraseña tiene al menos 6 caracteres la contraseña y la confirmación coinciden manejo de errores de registro nuestro manejador de registro incluye manejo de errores específicos de 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 devuelve códigos de error específicos que podemos usar para proporcionar comentarios útiles al usuario código 202 nombre de usuario ya tomado código 203 correo electrónico ya en uso el código completo para el registro de usuarios/inscripción se puede encontrar aquí implementando el inicio de sesión de usuario nuestro componente loginpage maneja la autenticación de usuarios utilizando parse user login() formulario de inicio de sesión el formulario de inicio de sesión recopila el nombre de usuario y la contraseña // 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> verificación de sesión como se mostró anteriormente, verificamos si hay una sesión existente cuando se carga la página de inicio de sesión // 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]); esta es una característica clave de parse gestiona automáticamente el token de sesión en el almacenamiento del navegador, lo que nos permite verificar fácilmente si un usuario ya ha iniciado sesión implementación de restablecimiento de contraseña back4app proporciona un flujo de restablecimiento de contraseña integrado en nuestra aplicación, enlazamos a una página de restablecimiento de contraseña desde el formulario de inicio de sesión // from loginpage js \<link as={routerlink} to="/reset password" alignself="flex end" fontsize="sm"> forgot password? \</link> el proceso de restablecimiento de contraseña en back4app funciona de la siguiente manera el usuario solicita un restablecimiento de contraseña con su correo electrónico parse envía un enlace especial de restablecimiento al correo electrónico del usuario el usuario hace clic en el enlace y establece una nueva contraseña parse actualiza el hash de la contraseña en la base de datos para implementar esto en nuestra aplicación, usaríamos // example password reset implementation try { await parse user requestpasswordreset(email); // show success message } catch (error) { // handle error } asegurando rutas para usuarios autenticados para proteger ciertas rutas en nuestra aplicación, usamos un componente protectedroute que verifica si un usuario está autenticado // 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; } este componente utiliza nuestro authcontext para verificar si un usuario ha iniciado sesión muestra un spinner de carga mientras verifica redirige a la página de inicio de sesión si no se encuentra ningún usuario renderiza el contenido protegido si un usuario está autenticado usamos este componente en nuestra configuración de enrutamiento // from app js \<route path="/feed" element={ \<protectedroute> \<feedpage /> \</protectedroute> } /> configuración de autenticación de back4app back4app proporciona varias opciones de configuración para la autenticación en el panel de control verificación de correo electrónico puedes requerir verificación de correo electrónico antes de que los usuarios puedan iniciar sesión configura esto en "configuración del servidor" > "servidor parse" > "autenticación de usuario" política de contraseña establecer requisitos de longitud mínima y complejidad de la contraseña configura esto en "configuración del servidor" > "servidor parse" > "autenticación de usuario" duración de la sesión controla cuánto tiempo permanecen válidas las sesiones de usuario configura esto en "configuración del servidor" > "servidor parse" > "configuración de sesión" plantillas de correo electrónico personaliza los correos electrónicos de verificación y restablecimiento de contraseña configura esto en "configuración de la aplicación" > "plantillas de correo electrónico" prueba de tu implementación de autenticación para asegurar que tu sistema de autenticación funcione correctamente prueba de registro de usuario intenta registrarte con credenciales válidas intenta registrarte con un nombre de usuario existente (debería mostrar un error) verifica si el usuario aparece en tu panel de back4app bajo la clase " user" prueba de inicio de sesión de usuario intenta iniciar sesión con credenciales correctas (debería redirigir al feed) intenta iniciar sesión con credenciales incorrectas (debería mostrar un error) prueba de persistencia de sesión inicia sesión y actualiza la página (debería permanecer conectado) cierra y vuelve a abrir el navegador (debería permanecer conectado si la sesión es válida) prueba de rutas protegidas intenta acceder a /feed cuando estás desconectado (debería redirigir a inicio de sesión) intenta acceder a /feed cuando estás conectado (debería mostrar la página del feed) el código para el componente de inicio de sesión se puede encontrar aquí paso 4 — desarrollando la funcionalidad del feed en este paso, implementarás la característica central de la red social el feed aquí es donde los usuarios crearán publicaciones, verán contenido de otros e interactuarán a través de me gusta y comentarios usaremos el parse server de back4app para almacenar y recuperar publicaciones, manejar cargas de archivos para imágenes e implementar actualizaciones en tiempo real entendiendo la estructura de la página de feed la página de feed en nuestra aplicación tiene tres componentes principales una barra lateral para la navegación el área principal de feed con creación de publicaciones y listado de publicaciones una sección de tendencias (en pantallas más grandes) examinemos cómo se implementa esto en nuestro 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> ); } este diseño responsivo se adapta a diferentes tamaños de pantalla, ocultando las barras laterales en dispositivos móviles creando la clase de publicación en back4app antes de implementar el frontend, asegurémonos de que nuestra base de datos de back4app esté correctamente configurada para las publicaciones la clase post debe tener los siguientes campos contenido (cadena) el contenido de texto de la publicación imagen (archivo) adjunto de imagen opcional autor (puntero a usuario) el usuario que creó la publicación me gusta (número) conteo de me gusta en la publicación gustadopor (array) array de ids de usuarios que les gustó la publicación creadoen (fecha) agregado automáticamente por parse establecer permisos apropiados para la clase post acceso de lectura público todos deberían poder leer publicaciones acceso de escritura público los usuarios autenticados deberían poder crear publicaciones permisos de actualización/eliminación solo el autor debería poder modificar sus publicaciones implementación de la creación de publicaciones examinemos cómo se implementa la creación de publicaciones en nuestro componente 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); } }; puntos clave sobre la creación de publicaciones manejo de archivos en parse parse file se utiliza para subir imágenes al almacenamiento de back4app el archivo se guarda primero, luego se adjunta al objeto de publicación back4app maneja automáticamente el almacenamiento de archivos y genera urls creando objetos parse extendemos la clase 'post' con parse object extend('post') creamos una nueva instancia con new post() establecemos propiedades usando el set() método guardamos el objeto en back4app con save() asociación de usuarios asociamos la publicación con el usuario actual usando parse user current() esto crea una relación de puntero en la base de datos el formulario de creación de publicaciones se ve así {/ 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> obteniendo y mostrando publicaciones ahora veamos cómo obtenemos y mostramos publicaciones de 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(); }, \[]); puntos clave sobre la obtención de publicaciones consultas de parse creamos una consulta con new parse query(post) incluimos objetos relacionados con query include('author') ordenamos con query descending('createdat') paginar con query limit() y query skip() ejecutamos la consulta con query find() procesamiento de resultados los objetos de parse tienen un get() método para acceder a propiedades para campos de archivo, usamos file url() para obtener la url transformamos objetos de parse en objetos de javascript simples para el estado de react paginación implementamos la funcionalidad de "cargar más" con seguimiento de páginas verificamos si hay más publicaciones para cargar antes de hacer solicitudes adicionales las publicaciones se muestran en una lista {/ 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> )} implementando la funcionalidad de me gusta examinemos cómo se implementa la funcionalidad de 'me gusta' // 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', }); } }; puntos clave sobre la funcionalidad de 'me gusta' actualizaciones optimistas actualizamos la interfaz de usuario inmediatamente antes de que el servidor confirme el cambio esto hace que la aplicación se sienta más receptiva actualizaciones de objetos de análisis obtenemos la publicación específica con query get(postid) modificamos sus propiedades con post set() guardamos los cambios con post save() seguimiento de 'me gusta' mantenemos tanto un conteo ( me gusta ) como una lista de usuarios ( likedby ) esto nos permite mostrar conteos precisos y determinar si el usuario actual ha dado 'me gusta' a una publicación implementación de actualizaciones en tiempo real con livequery (opcional) para hacer que la actualización del feed sea en tiempo real cuando se crean nuevas publicaciones, podemos usar 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(); } }; }, \[]); puntos clave sobre livequery configuración de suscripción creamos una consulta y nos suscribimos a ella con query subscribe() esto establece una conexión websocket con el servidor livequery de back4app manejo de eventos escuchamos eventos de 'crear' cuando se crean nuevas publicaciones escuchamos eventos de 'actualizar' cuando se modifican publicaciones actualizamos nuestro estado local en consecuencia limpieza nos desuscribimos cuando el componente se desmonta para prevenir fugas de memoria optimizando la carga de publicaciones con paginación ya hemos implementado la paginación básica con el botón "cargar más" mejoremos esto con desplazamiento infinito // 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); } }; y añade esto al final de la lista de publicaciones {/ infinite scroll trigger /} {hasmore && ( \<box id="load more trigger" h="20px" /> )} puntos clave sobre el desplazamiento infinito intersection observer usamos la api de intersection observer para detectar cuando el usuario se desplaza hasta el final cuando el elemento de activación se vuelve visible, cargamos más publicaciones estados de carga seguimos estados de carga separados para la carga inicial y "cargar más" esto previene múltiples solicitudes simultáneas consideraciones de rendimiento solo cargamos un número fijo de publicaciones a la vez (paginación) verificamos si hay más publicaciones para cargar antes de hacer solicitudes adicionales optimización del rendimiento de back4app para optimizar el rendimiento al trabajar con back4app usar índices agrega índices a los campos consultados con frecuencia en tu panel de back4app para la clase post, agrega índices en 'createdat' y 'author' consultas selectivas usa query select() para obtener solo los campos que necesitas esto reduce la transferencia de datos y mejora el rendimiento optimización de conteo en lugar de obtener todas las publicaciones para contarlas, usa query count() esto es más eficiente para determinar los conteos totales paso 6 — agregando interacciones sociales en este paso, mejoraremos nuestra red social implementando características clave de interacción social comentarios en publicaciones, perfiles de usuario y configuraciones de usuario nos centraremos en cómo estas características interactúan con el backend de back4app y los mecanismos que las hacen funcionar implementación de comentarios en publicaciones los comentarios son una característica fundamental de interacción social que requiere un modelado de datos adecuado en back4app examinemos cómo nuestra aplicación interactúa con el parse server para implementar comentarios modelo de datos de back4app para comentarios en back4app, los comentarios se implementan como una clase separada con relaciones tanto a usuarios como a publicaciones estructura de la clase comentario contenido (string) el contenido de texto del comentario autor (puntero a usuario) apunta al usuario que creó el comentario publicación (puntero a publicación) apunta a la publicación en la que se comenta createdat (fecha) gestionado automáticamente por parse tipos de relaciones usuario → comentarios uno a muchos (un usuario puede crear muchos comentarios) publicación → comentarios uno a muchos (una publicación puede tener muchos comentarios) recuperando comentarios de back4app nuestra postdetailspage utiliza consultas de parse para obtener comentarios de una publicación específica // 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', }); } }; mecanismos clave de back4app parse object extend() crea una referencia a la clase comment en back4app query equalto() crea una restricción para encontrar solo comentarios para una publicación específica query include() realiza una operación similar a un join para obtener objetos relacionados en una sola consulta query descending() ordena los resultados por un campo específico creando comentarios en back4app cuando un usuario agrega un comentario, creamos un nuevo objeto parse y establecemos las relaciones // 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); } }; mecanismos clave de back4app new comment() crea una nueva instancia de la clase comment comment set() establece propiedades en el objeto parse, incluyendo punteros a objetos relacionados comment save() envía el objeto a back4app para almacenamiento parse user current() obtiene el usuario autenticado actualmente para establecer la relación de autor seguridad de back4app para comentarios para asegurar adecuadamente los comentarios en back4app configurar permisos a nivel de clase (clps) leer público (todos pueden leer comentarios) escribir solo usuarios autenticados (solo los usuarios que han iniciado sesión pueden comentar) actualizar/eliminar solo el creador (solo el autor del comentario puede modificar o eliminar) configura estos permisos en tu panel de back4app { "find" { " " true }, "get" { " " true }, "create" { " " true }, "update" { "requiresauthentication" true }, "delete" { "requiresauthentication" true }, "addfield" { "requiresauthentication" true } } paso 7 creando perfiles de usuario con back4app los perfiles de usuario en nuestra aplicación aprovechan la clase de usuario incorporada de parse con campos personalizados examinemos cómo profilepage js interactúa con back4app extensiones de la clase de usuario de back4app la clase de usuario de parse se extiende con campos adicionales para nuestra red social campos de usuario personalizados avatar (archivo) foto de perfil almacenada en el almacenamiento de archivos de back4app bio (cadena) biografía del usuario website (cadena) url del sitio web del usuario displayname (cadena) nombre de visualización del usuario obteniendo datos de usuario y publicaciones nuestra profilepage obtiene tanto los datos del usuario como las publicaciones del usuario // 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); } }; mecanismos clave de back4app parse user current() recupera el usuario autenticado del token de sesión query equalto('author', currentuser) crea una restricción de igualdad de puntero para encontrar publicaciones del usuario actual post get('image') url() accede a la url de un objeto de archivo de parse almacenado en back4app implementando configuraciones de usuario la página de configuración permite a los usuarios actualizar su información de perfil y gestionar la configuración de la cuenta examinemos cómo interactúa con 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> ); } mecanismos clave de back4app parse user current() obtiene el usuario actual para actualizar sus configuraciones currentuser set() actualiza las propiedades del usuario en el objeto parse user currentuser save() persiste los cambios en back4app esquema de configuración de usuario de back4app para implementar configuraciones en back4app agrega estos campos a la clase usuario ajustesdeprivacidad (objeto) objeto json que contiene preferencias de privacidad ajustesdeseguridad (objeto) objeto json que contiene configuraciones de seguridad ajustesdenotificación (objeto) objeto json que contiene preferencias de notificación ejemplo de esquema para estos objetos // ajustesdeprivacidad { "visibilidaddelperfil" "público", // o "amigos" o "privado" "privacidaddepublicaciones" "amigos", // o "público" o "privado" "mostraractividad" true } // ajustesdeseguridad { "autenticacióndedosfactores" false, "alertasdeiniciodesesión" true } // ajustesdenotificación { "megusta" true, "comentarios" true, "seguimientos" true, "mensajes" true } funciones en la nube de back4app para interacciones sociales para interacciones sociales más complejas, puedes implementar cloud functions en back4app por ejemplo, para rastrear notificaciones de comentarios // 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 }); }); para implementar esto ve a tu panel de back4app navega a "cloud code" > "cloud functions" crea una nueva función con el código anterior despliega la función paso 8 — construyendo mensajería en tiempo real en este paso, implementaremos la funcionalidad de mensajería en tiempo real utilizando la función livequery de back4app esto permitirá a los usuarios intercambiar mensajes instantáneamente sin refrescar la página, creando una experiencia de chat dinámica similar a las plataformas de mensajería populares entendiendo livequery de back4app antes de sumergirnos en la implementación, entendamos cómo funciona livequery de back4app ¿qué es livequery? livequery es una característica de parse server que permite a los clientes suscribirse a consultas cuando los objetos que coinciden con estas consultas cambian, el servidor envía automáticamente actualizaciones a los clientes suscritos esto crea funcionalidad en tiempo real sin implementar tú mismo un manejo complejo de websocket cómo funciona livequery livequery establece una conexión websocket entre el cliente y el servidor los clientes se suscriben a consultas específicas que desean monitorear cuando los datos que coinciden con estas consultas cambian, el servidor envía eventos a través del websocket el cliente recibe estos eventos y actualiza la interfaz de usuario en consecuencia eventos de livequery crear se activa cuando se crea un nuevo objeto que coincide con la consulta actualizar se activa cuando un objeto existente que coincide con la consulta es actualizado entrar se activa cuando un objeto comienza a coincidir con la consulta dejar se activa cuando un objeto ya no coincide con la consulta eliminar se activa cuando un objeto que coincide con la consulta es eliminado configurando livequery en back4app para habilitar livequery para su aplicación, siga estos pasos habilita tu subdominio de back4app inicia sesión en tu cuenta de back4app navega a "configuración de la aplicación" > "configuración del servidor" encuentra el bloque "url del servidor y consulta en vivo" y haz clic en "ajustes" verifica la opción "activar tu subdominio de back4app" este subdominio servirá como su servidor livequery activar livequery en la misma página de configuración, marca la opción "activar consulta en vivo" selecciona las clases que deseas monitorear con livequery mensaje (para mensajes de chat) estado de escritura (para indicadores de escritura) conversación (para actualizaciones de conversación) guarda tus cambios nota su url del servidor livequery la url de su servidor livequery estará en el formato wss\ //yourappname back4app io necesitarás esta url para inicializar el cliente livequery en tu aplicación react configurando livequery en tu aplicación react para usar livequery en tu aplicación react, necesitas inicializar un cliente de 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' en tu env local archivo, asegúrate de incluir react app parse live query url=wss\ //yourappname back4app io creando el modelo de datos para mensajería nuestro sistema de mensajería requiere dos clases principales en back4app clase de conversación participantes (array) array de punteros de usuario para los usuarios en la conversación últimomensaje (cadena) el contenido del mensaje más reciente fechadelúltimomensaje (fecha) marca de tiempo del mensaje más reciente actualizadoen (fecha) gestionado automáticamente por parse clase de mensaje conversación (puntero) apunta a la conversación a la que pertenece este mensaje remitente (puntero) apunta al usuario que envió el mensaje contenido (cadena) el contenido de texto del mensaje leer (booleano) si el mensaje ha sido leído creadoen (fecha) gestionado automáticamente por parse clase typingstatus conversación (puntero) apunta a la conversación usuario (puntero) apunta al usuario que está escribiendo estáescribiendo (booleano) si el usuario está escribiendo actualmente implementando la interfaz de mensajería examinemos cómo nuestra messagespage implementa la mensajería en tiempo real // 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 } el componente mantiene varias piezas de estado conversations lista de las conversaciones del usuario selectedconversation la conversación actualmente seleccionada messages mensajes en la conversación seleccionada typingusers usuarios que están escribiendo actualmente en la conversación también utiliza refs para almacenar suscripciones de livequery y gestionar indicadores de escritura suscribiéndose a livequery para mensajes la clave para la mensajería en tiempo real es suscribirse a livequery para mensajes en la conversación actual // 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); } }; mecanismos clave de livequery creando una consulta creamos una consulta para los mensajes en la conversación actual suscribiéndose a la consulta llamamos query subscribe() para comenzar a escuchar cambios manejando eventos usamos subscription on('create', callback) para manejar nuevos mensajes darse de baja almacenamos la referencia de suscripción y nos damos de baja cuando es necesario implementando indicadores de escritura con livequery los indicadores de escritura son otra característica en tiempo real implementada con 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); } }; actualizando el estado de escritura cuando un usuario escribe, actualizamos su estado de escritura // 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); }; enviando mensajes al enviar mensajes, creamos un nuevo objeto mensaje y dejamos que livequery maneje las actualizaciones // 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, }); } }; limpiando suscripciones de livequery es importante limpiar las suscripciones de livequery cuando ya no son necesarias // 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); } }; }, \[]); consideraciones de rendimiento de livequery en back4app al implementar livequery, considera estos consejos de rendimiento sé específico con las consultas suscríbete solo a los datos que necesitas utiliza restricciones para limitar el alcance de las suscripciones por ejemplo, suscríbete solo a los mensajes en la conversación actual gestiona las suscripciones con cuidado cancela la suscripción cuando los datos ya no sean necesarios crea nuevas suscripciones cuando cambie el contexto almacena referencias de suscripción para cancelar la suscripción más tarde usa acls para seguridad establece acls adecuadas en los objetos de mensaje y conversación asegúrate de que los usuarios solo puedan acceder a las conversaciones de las que forman parte livequery respeta las acls, por lo que los usuarios no autorizados no recibirán actualizaciones optimiza el servidor livequery en el panel de control de back4app, configura las clases que necesitan livequery no habilites livequery para clases que no necesitan actualizaciones en tiempo real paso 9 — implementación de la funcionalidad de búsqueda en este paso, implementaremos una funcionalidad de búsqueda integral para nuestra red social los usuarios podrán buscar otros usuarios, publicaciones por contenido y hashtags esta función facilitará a los usuarios descubrir contenido y conectarse con otros en la plataforma entendiendo la búsqueda en back4app antes de sumergirnos en la implementación, entendamos cómo funciona la búsqueda en back4app sistema de consulta de parse back4app utiliza el sistema de consulta de parse server para buscar las consultas se pueden realizar en múltiples clases puedes buscar por coincidencia exacta, contiene, comienza con, etc opciones de búsqueda de texto comienzacon encuentra cadenas que comienzan con una cadena específica contiene encuentra cadenas que contienen una subcadena específica coincide utiliza expresiones regulares para coincidencias de patrones más complejas textocompleto (función de empresa) proporciona capacidades avanzadas de búsqueda de texto completo consideraciones de rendimiento las búsquedas de texto pueden ser intensivas en recursos se deben crear índices para campos que se buscan con frecuencia las consultas deben optimizarse para limitar el número de resultados construyendo la página de búsqueda nuestro componente searchpage manejará diferentes tipos de búsquedas y mostrará los resultados examinemos su estructura // 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 } el componente mantiene el estado para la consulta de búsqueda ingresada por el usuario el tipo de búsqueda que se está realizando los resultados de búsqueda estado de carga temas de tendencia implementando la búsqueda de usuarios veamos cómo buscamos usuarios en 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); } }; mecanismos clave de back4app new parse query(parse user) crea una consulta en la clase user userquery matches('username', new regexp(query, 'i')) realiza una coincidencia regex sin distinción de mayúsculas y minúsculas en los nombres de usuario userquery limit(20) limita los resultados para mejorar el rendimiento userquery find() ejecuta la consulta y devuelve los usuarios coincidentes implementando la búsqueda de contenido de publicaciones ahora veamos cómo buscamos publicaciones por contenido // 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); } }; mecanismos clave de back4app parse object extend('post') hace referencia a la clase post postquery matches('content', new regexp(query, 'i')) realiza una coincidencia regex sin distinción entre mayúsculas y minúsculas en el contenido de la publicación postquery include('author') incluye la información del autor en una sola consulta postquery descending('createdat') ordena los resultados por fecha de creación implementación de búsqueda por hashtag la búsqueda por hashtag requiere un enfoque diferente buscaremos publicaciones que contengan hashtags // 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); } }; mecanismos clave de back4app usamos una expresión regular con límites de palabras ( \\\b ) para encontrar hashtags reales este enfoque encuentra publicaciones donde el contenido contiene el hashtag específico implementando temas de tendencia para implementar temas de tendencia, necesitamos analizar publicaciones recientes y contar las ocurrencias de hashtags // 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); } }; mecanismos clave de back4app consultamos publicaciones de los últimos 7 días usando query greaterthan('createdat', oneweekago) analizamos el contenido para extraer y contar hashtags ordenamos por frecuencia para encontrar los hashtags más populares manejo de la ejecución de búsqueda ahora veamos cómo manejamos la ejecución de búsqueda según el tipo de búsqueda // 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); } }; optimizando la búsqueda en back4app para optimizar el rendimiento de búsqueda en back4app crear índices navega a tu panel de back4app ve a "explorador de base de datos" > selecciona la clase (por ejemplo, usuario, publicación) haz clic en la pestaña "índices" crea índices para campos que se buscan con frecuencia para la clase usuario crea un índice en nombre de usuario para la clase publicación crea un índice en contenido usar restricciones de consulta siempre usa limit() para restringir el número de resultados usa select() para solo obtener los campos que necesitas usa skip() para paginación al tratar con grandes conjuntos de resultados considera funciones en la nube para búsquedas complejas para lógica de búsqueda más compleja, implementa una función en la nube esto te permite realizar procesamiento del lado del servidor y devolver resultados optimizados ejemplo de función en la nube para búsqueda avanzada // 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; }); paso 10 — probando y desplegando tu red social en este paso final, cubriremos cómo probar tu aplicación, prepararla para producción, desplegarla en un servicio de alojamiento y monitorear y escalar tu backend de back4app estos pasos son cruciales para asegurar que tu red social funcione sin problemas en un entorno de producción probando tu aplicación antes de desplegar, es importante probar a fondo tu aplicación para detectar cualquier error o problema 1\ pruebas manuales crea un plan de pruebas que cubra todas las características clave de tu aplicación autenticación de usuario prueba de registro con entradas válidas e inválidas prueba de inicio de sesión con credenciales correctas e incorrectas probar la funcionalidad de restablecimiento de contraseña probar la persistencia de sesión y cierre de sesión funcionalidad de publicación prueba crear publicaciones con texto e imágenes prueba de visualización de publicaciones en el feed prueba de dar me gusta y comentar en publicaciones prueba eliminar publicaciones interacciones sociales prueba de visualización de perfiles de usuario prueba de comentar en publicaciones prueba de mensajería en tiempo real funcionalidad de búsqueda prueba de búsqueda de usuarios prueba de búsqueda de publicaciones buscar por hashtag de prueba pruebas entre navegadores prueba en chrome, firefox, safari y edge prueba en navegadores móviles 2\ pruebas automatizadas para pruebas más robustas, implementa pruebas automatizadas // 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\ pruebas de back4app prueba tu configuración de back4app funciones en la nube prueba todas las funciones en la nube con varias entradas seguridad verifica que los permisos a nivel de clase estén funcionando correctamente livequery prueba la funcionalidad en tiempo real con múltiples clientes preparándose para producción antes de desplegar, optimiza tu aplicación para producción 1\ configuración del entorno crea archivos de entorno separados para desarrollo y producción \# 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\ optimización de la construcción optimiza tu construcción de react // in your package json "scripts" { "analyze" "source map explorer 'build/static/js/ js'", "build" "generate sourcemap=false react scripts build" } instala el source map explorer para analizar el tamaño de tu paquete npm install save dev source map explorer 3\ optimización del rendimiento implementar la división de código para reducir el tiempo de carga inicial // 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> ); } despliegue en un servicio de hosting hay varias opciones para desplegar tu aplicación react 1\ despliegue en vercel vercel es una gran opción para aplicaciones de react instala el cli de vercel npm install g vercel despliega tu aplicación vercel para el despliegue en producción vercel prod 2\ desplegando en netlify netlify es otra excelente opción instala el cli de netlify npm install g netlify cli construye tu aplicación npm run build despliega en netlify netlify deploy para el despliegue en producción netlify deploy prod 3\ desplegando en github pages para una opción de implementación simple instalar gh pages npm install save dev gh pages agregar a package json "homepage" "https //yourusername github io/your repo name", "scripts" { "predeploy" "npm run build", "deploy" "gh pages d build" } desplegar npm run deploy monitoreo y escalado de su backend de back4app a medida que su red social crece, necesitará monitorear y escalar su backend de back4app 1\ monitoreo del rendimiento back4app proporciona varias herramientas para monitorear su aplicación análisis del panel monitorear solicitudes de api, uso de almacenamiento y operaciones de archivos registros verificar los registros del servidor en busca de errores y problemas de rendimiento métricas de rendimiento rastrear tiempos de respuesta e identificar cuellos de botella para acceder a estas herramientas ve a tu panel de back4app navega a "análisis" para estadísticas de uso verifica "registros" para registros de operaciones detallados 2\ escalando tu backend cuando tu base de usuarios crece, es posible que necesites escalar tu backend de back4app actualiza tu plan cambia a un plan de nivel superior con más recursos optimiza consultas usa índices y limita las consultas para mejorar el rendimiento implementa caché usa caché del lado del cliente para datos de acceso frecuente 3\ optimización de base de datos optimiza tu base de datos para un mejor rendimiento crear índices agrega índices a los campos consultados con frecuencia // ejemplo creando un índice en el campo 'username' en la clase user const schema = new parse schema(' user'); schema addindex('username index', { username 1 }); schema update(); usar pipeline de agregación para operaciones de datos complejas // ejemplo contar publicaciones por usuario const pipeline = \[ { group { objectid '$author', count { $sum 1 } } } ]; const results = await parse cloud aggregate('post', pipeline); 4\ implementación de un cdn para medios para una entrega más rápida de imágenes y medios configura un cdn como cloudflare o amazon cloudfront actualiza la configuración de almacenamiento de archivos de back4app para usar el cdn actualiza las url de archivos en tu aplicación para usar el dominio del cdn 5\ configuración de alertas de monitoreo configura alertas para ser notificado de problemas ve a tu panel de back4app navega a "configuración de la app" > "alertas" configura alertas para uso alto de api picos en la tasa de errores límites de tamaño de base de datos tiempo de inactividad del servidor resumen de lo que se logró a lo largo de este tutorial, has configurar una infraestructura de backend robusta creó una cuenta de back4app y configuró su aplicación diseñé un esquema de base de datos para usuarios, publicaciones, comentarios y mensajes configuración de la seguridad y permisos a nivel de clase configurar livequery para funcionalidad en tiempo real construyó un frontend moderno de react creé una interfaz de usuario receptiva con componentes de chakra ui implementado enrutamiento del lado del cliente con react router desarrollé componentes reutilizables para publicaciones, comentarios y perfiles de usuario conectaste tu frontend a back4app usando el sdk de javascript de parse implementadas las características principales de la red social autenticación de usuario (registro, inicio de sesión, restablecimiento de contraseña) creación de publicaciones e interacción (me gusta, comentarios) perfiles de usuario y configuraciones mensajería en tiempo real entre usuarios funcionalidad de búsqueda para usuarios, publicaciones y hashtags optimizado para producción se implementaron optimizaciones de rendimiento como la división de código configurar las configuraciones del entorno para desarrollo y producción aprendí a desplegar tu aplicación en servicios de alojamiento exploró estrategias de monitoreo y escalado para su backend de back4app ahora tienes una base sólida para una aplicación de red social que se puede ampliar y personalizar para satisfacer tus necesidades específicas próximos pasos para extender la aplicación aquí hay algunas formas emocionantes de mejorar tu aplicación de red social funciones avanzadas de medios agregar soporte para cargas y reproducción de videos implementar filtros de imagen y herramientas de edición crea historias o características de contenido efímero agregar soporte para gifs y otros medios enriquecidos interacciones sociales mejoradas implementar un motor de recomendaciones para sugerencias de amigos agregar funcionalidad de grupos o comunidades crea eventos con capacidades de rsvp desarrollar un sistema de notificación para todas las interacciones de los usuarios opciones de monetización implementar características de membresía premium agregar compras dentro de la aplicación para bienes digitales crea un mercado para transacciones de usuario a usuario integrarse con procesadores de pago como stripe experiencia móvil convierte tu aplicación en una aplicación web progresiva (pwa) desarrollar aplicaciones móviles nativas utilizando react native implementar notificaciones push para dispositivos móviles optimizar la interfaz de usuario para diferentes tamaños de pantalla y orientaciones análisis y perspectivas integrar herramientas de análisis para rastrear la participación del usuario crea paneles para el rendimiento del contenido implementar pruebas a/b para nuevas funciones desarrollar conocimientos sobre el comportamiento del usuario para mejorar la plataforma moderación de contenido implementar filtrado automático de contenido crear sistemas de informes para contenido inapropiado desarrollar herramientas de administración para la moderación de contenido utiliza el aprendizaje automático para el análisis inteligente de contenido recursos adicionales para aprender para seguir ampliando tus conocimientos y habilidades, aquí hay algunos recursos valiosos documentación y tutoriales de back4app documentación de back4app https //www back4app com/docs/get started/welcome guía de javascript de parse https //docs parseplatform org/js/guide/ canal de youtube de back4app https //www youtube com/c/back4app react y javascript moderno documentación de react https //reactjs org/docs/getting started html javascript info https //javascript info/ cursos de react en egghead io https //egghead io/q/react diseño de ui y ux documentación de chakra ui https //chakra ui com/docs/getting started patrones de diseño de ui https //ui patterns com/ investigación de ux del grupo nielsen norman https //www nngroup com/articles/ optimización del rendimiento rendimiento de web dev https //web dev/performance scoring/ optimización del rendimiento de react https //reactjs org/docs/optimizing performance html google pagespeed insights https //developers google com/speed/pagespeed/insights/ comunidad y apoyo stack overflow https //stackoverflow\ com/questions/tagged/parse platform foro de la comunidad parse https //community parseplatform org/ comunidad de desarrolladores de react https //dev to/t/react recuerda que construir una red social exitosa es un proceso iterativo comienza con una base sólida (que ahora tienes), recopila comentarios de los usuarios y mejora continuamente tu aplicación en función de los patrones de uso del mundo real esperamos que este tutorial te haya proporcionado el conocimiento y la confianza para construir aplicaciones increíbles con react y back4app ¡feliz codificación!