Project Templates
Social Network
How to build a backend for a social network?
49 min
introducción en esta guía, descubrirás cómo configurar un backend robusto para una red social utilizando una plataforma de backend como servicio este backend estará diseñado para manejar la autenticación de usuarios, la gestión de datos, la comunicación en tiempo real y más, todo mientras se asegura la seguridad y la eficiencia al final de este tutorial, tu proyecto se verá así descripción general de la red social consejo para el código fuente completo, visita nuestro repositorio de github en github https //github com/templates back4app/back4gram conclusiones clave gestión de backend simplificada aprovecha el backend gestionado de back4app para optimizar la autenticación de usuarios, el almacenamiento de datos y las funcionalidades en tiempo real interacciones en tiempo real utiliza livequery para habilitar mensajería dinámica y actualizaciones de contenido inmediatas manejo integral de usuarios y contenidos establece sistemas robustos para perfiles de usuario, publicaciones, comentarios e interacciones sociales escalabilidad y rendimiento aplica optimización de consultas, almacenamiento en caché y operaciones por lotes para mantener un alto rendimiento a medida que tu red crece integraciones avanzadas mejora tu backend con funciones en la nube, trabajos en segundo plano y notificaciones push para una experiencia de usuario más rica requisitos previos antes de comenzar, asegúrate de tener una cuenta de back4app regístrate gratis en back4app com https //www back4app com/ node js y npm instala node js (v14 x o más reciente) desde nodejs org https //nodejs org/ conocimientos básicos de javascript y react un editor de código usa editores como visual studio code o sublime text paso 1 — configurando tu backend de back4app creando un nuevo proyecto en back4app inicie sesión en su panel de back4app haga clic en "crear una nueva aplicación" nombre su aplicación (por ejemplo, "back4social") y elija la región del servidor más cercana a usted una vez que se crea el proyecto, lo verá listado en su panel de back4app este proyecto será la base para todas las configuraciones de backend discutidas en este tutorial diseñando el esquema de la base de datos el backend de tu red social utilizará las siguientes clases usuario (proporcionado por parse por defecto) extender con campos como biografía y foto de perfil publicación contiene contenido de texto y cargas de imágenes comentario almacena comentarios de usuarios en publicaciones conversación representa una sesión de chat entre usuarios mensaje contiene mensajes individuales en una conversación estadodeescritura indica cuando un usuario está escribiendo añadiendo clases de base de datos en back4app, los datos se almacenan en clases puedes crear una nueva clase en el panel de control de back4app o a través de un agente de ia para crear utilizando el panel de control, por favor navega a la sección “base de datos” en tu panel de control de back4app crea una nueva clase y añade columnas relevantes, como título (string) y iscompleted (boolean) tu backend de red social utilizará las siguientes clases para configurar tu base de datos ve a la "base de datos" sección en tu panel de control de back4app mejorar la clase usuario abre la clase existente usuario agregar columnas biografía (cadena) avatar (archivo) seguidores (número, predeterminado 0) siguiente (número, predeterminado 0) crear la clase post haga clic en "crear una clase" nómbralo publicación y añade contenido (cadena) autor (puntero a usuario ) imagen (archivo) gustos (número, predeterminado 0) gustadopor (array) crear la clase comentario nómbralo comentario e incluye contenido (cadena) autor (puntero a usuario ) publicar (puntero a publicación) crea la clase conversation nómbralo conversación y añade participantes (array) últimomensaje (cadena) crea la clase message nómbralo mensaje y añade texto (cadena) remitente (puntero a usuario ) conversación (puntero a conversación) crea la clase typingstatus nómbralo typingstatus y añade usuario (puntero a usuario ) conversación (puntero a conversación) está escribiendo (booleano) para crear utilizando el agente de ia, por favor abre el agente de ia desde tu panel de aplicaciones o el menú describe tu modelo de datos en lenguaje sencillo deja que el agente de ia cree el esquema automáticamente configurando permisos de clase para proteger tus datos, ajusta los permisos de nivel de clase (clps) ve a "seguridad y claves" en tu panel bajo "seguridad de nivel de clase" , establece reglas de lectura/escritura por ejemplo, permite acceso público de lectura para publicar mientras restringes las operaciones de actualización/eliminación al autor de la publicación habilitando características en tiempo real con livequery en "configuraciones del servidor" , localiza parse server y activa livequery incluye estas clases para monitoreo en tiempo real mensaje estado de escritura publicación (para actualizar me gusta y comentarios en tiempo real) configuración de livequery recupera tus claves de aplicación navega a "configuraciones de la app" > "seguridad y claves" guarda tu id de aplicación , clave de javascript , url del servidor , y url del servidor de livequery claves de seguridad paso 2 — vinculando tu frontend con back4app configuración de variables de entorno crea un env local archivo en el directorio raíz de tu proyecto react app parse app id=your application id react app parse js key=your javascript key react app parse server url=https //parseapi back4app com react app parse live query url=wss\ //your app id back4app io reemplaza los marcadores de posición con tus credenciales reales configurando el sdk de parse crea un archivo de configuración para inicializar parse // src/utils/parseconfig js import parse from 'parse/dist/parse min js'; // initialize parse with your environment variables 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; // enable livequery if configured if (process env react app parse live query url) { parse livequeryserverurl = process env react app parse live query url; } export default parse; luego importa este archivo en el punto de entrada de tu aplicación (por ejemplo, index js ) // src/index js import react from 'react'; import reactdom from 'react dom/client'; import ' /index css'; import app from ' /app'; import ' /utils/parseconfig'; // initialize parse const root = reactdom createroot(document getelementbyid('root')); root render( \<react strictmode> \<app /> \</react strictmode> ); paso 3 — configuración de la autenticación de usuarios el servidor parse de back4app ofrece un sistema de gestión de usuarios integrado a través de la parse user clase cómo parse gestiona a los usuarios maneja las credenciales de usuario (nombre de usuario, correo electrónico, contraseña) gestiona el estado de autenticación y los tokens de sesión simplifica los procesos de registro e inicio de sesión ejemplo de registro de usuario // function to handle user sign up const handlesignup = async () => { try { const user = new parse user(); user set('username', username); user set('email', email); user set('password', password); // set additional user info user set('bio', ''); user set('followers', 0); user set('following', 0); await user signup(); console log('registration successful'); navigate('/feed'); } catch (error) { console error('sign up error ', error message); // handle errors based on error codes if (error code === 202) { seterrors({ errors, username 'username is taken'}); } else if (error code === 203) { seterrors({ errors, email 'email already exists'}); } } }; ejemplo de inicio de sesión de usuario // function to handle user login const handlelogin = async () => { try { const user = await parse user login(username, password); console log('login successful ', user getusername()); navigate('/feed'); } catch (error) { console error('login error ', error message); setloginerror(error message); } }; verificando una sesión existente const checkcurrentuser = async () => { try { const currentuser = parse user current(); if (currentuser) { console log('logged in as ', currentuser getusername()); return currentuser; } return null; } catch (error) { console error('error checking user ', error); return null; } }; implementación de restablecimiento de contraseña const handlepasswordreset = async () => { try { await parse user requestpasswordreset(email); console log('reset email sent'); setresetemailsent(true); } catch (error) { console error('password reset error ', error message); setreseterror(error message); } }; paso 4 — manejo de publicaciones creando nuevas publicaciones const createpost = async () => { if (!postcontent trim() && !postimage) { console error('please add content or an image'); return; } try { const post = parse object extend('post'); const newpost = new post(); newpost set('content', postcontent); newpost set('author', parse user current()); newpost set('likes', 0); newpost set('likedby', \[]); if (postimage) { const parsefile = new parse file(postimage name, postimage); await parsefile save(); newpost set('image', parsefile); } await newpost save(); console log('post created successfully'); return newpost; } catch (error) { console error('error creating post ', error message); throw error; } }; paso 4 — manejo de publicaciones recuperando publicaciones const fetchposts = async (page = 0, limit = 10) => { try { const post = parse object extend('post'); const query = new parse query(post); query include('author'); query descending('createdat'); query limit(limit); query skip(page limit); const results = await query find(); const posts = results map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, likedby post get('likedby') || \[], createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return posts; } catch (error) { console error('error fetching posts ', error); throw error; } }; implementando la función de me gusta/no me gusta const togglelike = async (postid) => { try { const currentuser = parse user current(); const userid = currentuser id; const post = parse object extend('post'); const query = new parse query(post); const post = await query get(postid); const likedby = post get('likedby') || \[]; const isliked = likedby includes(userid); if (isliked) { post set('likedby', likedby filter(id => id !== userid)); post set('likes', math max((post get('likes') || 1) 1, 0)); } else { post set('likedby', \[ likedby, userid]); post set('likes', (post get('likes') || 0) + 1); } await post save(); return !isliked; } catch (error) { console error('error toggling like ', error); throw error; } }; paso 5 — habilitando comentarios en publicaciones agregando un comentario const addcomment = async (postid, commentcontent) => { if (!commentcontent trim()) { console error('comment cannot be empty'); return; } try { const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); const comment = parse object extend('comment'); const comment = new comment(); comment set('content', commentcontent); comment set('author', parse user current()); comment set('post', post); await comment save(); console log('comment added successfully'); return comment; } catch (error) { console error('error adding comment ', error); throw error; } }; recuperando comentarios para una publicación específica const fetchcomments = async (postid) => { try { const post = parse object extend('post'); const postquery = new parse query(post); const post = await postquery get(postid); const comment = parse object extend('comment'); const query = new parse query(comment); query equalto('post', post); query include('author'); query ascending('createdat'); const results = await query find(); const comments = results map(comment => ({ id comment id, content comment get('content'), createdat comment get('createdat'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null } })); return comments; } catch (error) { console error('error fetching comments ', error); throw error; } }; paso 6 — gestión de perfiles de usuario recuperando un perfil de usuario const fetchuserprofile = async (userid) => { try { const query = new parse query(parse user); const user = await query get(userid); const post = parse object extend('post'); const postsquery = new parse query(post); postsquery equalto('author', user); postsquery include('author'); postsquery descending('createdat'); const posts = await postsquery find(); const userdata = { id user id, username user get('username'), bio user get('bio') || '', avatar user get('avatar') ? user get('avatar') url() null, followers user get('followers') || 0, following user get('following') || 0, posts posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') })) }; return userdata; } catch (error) { console error('error fetching user profile ', error); throw error; } }; actualizando un perfil de usuario const updateuserprofile = async (profiledata) => { try { const currentuser = parse user current(); if (profiledata bio !== undefined) { currentuser set('bio', profiledata bio); } if (profiledata avatarfile) { const parsefile = new parse file('avatar jpg', profiledata avatarfile); await parsefile save(); currentuser set('avatar', parsefile); } await currentuser save(); console log('profile updated successfully'); return currentuser; } catch (error) { console error('error updating profile ', error); throw error; } }; paso 7 — implementación de mensajería en tiempo real la función livequery de back4app permite mensajería en tiempo real e indicadores de escritura creando una conversación const createconversation = async (participantids) => { try { const currentuser = parse user current(); const allparticipantids = \[ new set(\[currentuser id, participantids])]; const existingconversation = await findexistingconversation(allparticipantids); if (existingconversation) { return existingconversation; } const participantpointers = await promise all( allparticipantids map(async (id) => { const userquery = new parse query(parse user); return await userquery get(id); }) ); const conversation = parse object extend('conversation'); const conversation = new conversation(); conversation set('participants', participantpointers); conversation set('lastmessage', ''); await conversation save(); console log('conversation created successfully'); return conversation; } catch (error) { console error('error creating conversation ', error); throw error; } }; // helper to find an existing conversation (simplified for demo) const findexistingconversation = async (participantids) => { try { const conversation = parse object extend('conversation'); const query = new parse query(conversation); const results = await query find(); for (const conversation of results) { const participants = conversation get('participants') || \[]; const ids = participants map(p => p id); if (ids length === participantids length && ids every(id => participantids includes(id))) { return conversation; } } return null; } catch (error) { console error('error finding conversation ', error); return null; } }; enviando un mensaje const sendmessage = async (conversationid, messagetext) => { try { const currentuser = parse user current(); const conversation = parse object extend('conversation'); const conversationquery = new parse query(conversation); const conversation = await conversationquery get(conversationid); const message = parse object extend('message'); const message = new message(); message set('text', messagetext); message set('sender', currentuser); message set('conversation', conversation); message set('read', false); await message save(); conversation set('lastmessage', messagetext); await conversation save(); console log('message sent successfully'); return message; } catch (error) { console error('error sending message ', error); throw error; } }; suscribirse a actualizaciones de mensajes a través de livequery const subscribetomessages = async (conversationid, onnewmessage) => { try { const message = parse object extend('message'); const query = new parse query(message); const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; query equalto('conversation', conversationpointer); query include('sender'); const subscription = await query subscribe(); subscription on('create', (message) => { const newmessage = { id message id, text message get('text'), createdat message get('createdat'), sender { id message get('sender') id, username message get('sender') get('username'), avatar message get('sender') get('avatar') ? message get('sender') get('avatar') url() null }, read message get('read') }; onnewmessage(newmessage); if (message get('sender') id !== parse user current() id) { markmessageasread(message); } }); return subscription; } catch (error) { console error('error subscribing to messages ', error); throw error; } }; const markmessageasread = async (message) => { try { message set('read', true); await message save(); } catch (error) { console error('error marking message as read ', error); } }; implementando indicadores de escritura const updatetypingstatus = async (conversationid, istyping) => { try { const currentuser = parse user current(); const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; 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) { typingstatus set('istyping', istyping); } else { 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); } }; const subscribetotypingstatus = async (conversationid, ontypingstatuschange) => { try { const typingstatus = parse object extend('typingstatus'); const query = new parse query(typingstatus); const conversation = parse object extend('conversation'); const conversationpointer = new conversation(); conversationpointer id = conversationid; query equalto('conversation', conversationpointer); query include('user'); const subscription = await query subscribe(); subscription on('update', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); subscription on('create', (typingstatus) => { const user = typingstatus get('user'); const istyping = typingstatus get('istyping'); ontypingstatuschange({ userid user id, username user get('username'), istyping }); }); return subscription; } catch (error) { console error('error subscribing to typing status ', error); throw error; } }; paso 8 — agregar capacidades de búsqueda búsqueda de usuarios const searchusers = async (query, limit = 20) => { try { const userquery = new parse query(parse user); userquery matches('username', new regexp(query, 'i')); userquery limit(limit); const users = await userquery find(); const userresults = users map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null, bio user get('bio') || '' })); return userresults; } catch (error) { console error('error searching users ', error); throw error; } }; búsqueda de publicaciones const searchposts = async (query, limit = 20) => { try { const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(query, 'i')); postquery include('author'); postquery descending('createdat'); postquery limit(limit); const posts = await postquery find(); const postresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return postresults; } catch (error) { console error('error searching posts ', error); throw error; } }; búsqueda de hashtags const searchhashtags = async (tag, limit = 20) => { try { const hashtagquery = tag startswith('#') ? tag substring(1) tag; const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(`#${hashtagquery}\\\b`, 'i')); postquery include('author'); postquery descending('createdat'); postquery limit(limit); const posts = await postquery find(); const hashtagresults = posts map(post => ({ id post id, content post get('content'), image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat'), author { id post get('author') id, username post get('author') get('username'), avatar post get('author') get('avatar') ? post get('author') get('avatar') url() null } })); return hashtagresults; } catch (error) { console error('error searching hashtags ', error); throw error; } }; paso 9 — aprovechando las características avanzadas de back4app back4app ofrece herramientas avanzadas como cloud functions, trabajos en segundo plano y hooks para enriquecer tu backend funciones en la nube para lógica del lado del servidor // cloud function example to notify users when a comment is added parse cloud aftersave("comment", async (request) => { if (request original) return; const comment = request object; const post = comment get("post"); const commenter = request user; 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; 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 }); }); búsqueda avanzada con cloud code parse cloud define("advancedsearch", async (request) => { const { query, type, limit = 20 } = request params; if (!query) { throw new error("search query is required"); } let results = \[]; switch (type) { case 'users' const userquery = new parse query(parse user); userquery matches('username', new regexp(query, 'i')); userquery limit(limit); results = await userquery find({ usemasterkey true }); break; case 'posts' const post = parse object extend('post'); const postquery = new parse query(post); postquery matches('content', new regexp(query, 'i')); postquery include('author'); postquery limit(limit); results = await postquery find({ usemasterkey true }); break; default throw new error("invalid search type"); } return results; }); trabajos en segundo plano para tareas recurrentes parse cloud job("calculatetrendingtopics", async () => { const post = parse object extend("post"); const query = new parse query(post); const oneweekago = new date(); oneweekago setdate(oneweekago getdate() 7); query greaterthan('createdat', oneweekago); query limit(1000); const posts = await query find({ usemasterkey true }); const hashtagcounts = {}; posts foreach(post => { const content = post get('content') || ''; const hashtags = content match(/#(\w+)/g) || \[]; hashtags foreach(hashtag => { const tag = hashtag tolowercase(); hashtagcounts\[tag] = (hashtagcounts\[tag] || 0) + 1; }); }); const trendingarray = object entries(hashtagcounts) map((\[hashtag, count]) => ({ hashtag, count })) sort((a, b) => b count a count) slice(0, 10); const trendingtopics = parse object extend("trendingtopics"); const trending = new trendingtopics(); trending set("topics", trendingarray); trending set("calculatedat", new date()); await trending save(null, { usemasterkey true }); return "trending topics calculated successfully"; }); características avanzadas adicionales notificaciones push utiliza el servicio de notificaciones push de back4app para alertar a los usuarios sobre nuevos mensajes control de acceso basado en roles define roles para gestionar permisos para acciones como la moderación de contenido webhooks integra servicios externos activando webhooks desde cloud code paso 10 — optimización del rendimiento a medida que tu backend escala, la optimización del rendimiento es clave considera las siguientes técnicas indexación de base de datos crea índices en campos consultados con frecuencia (por ejemplo, author , createdat , username ) para mejorar el rendimiento de las consultas optimización de consultas optimiza las consultas seleccionando solo los campos necesarios y utilizando paginación para limitar los conjuntos de resultados operaciones por lotes utilice actualizaciones y eliminaciones por lotes para minimizar las llamadas a la api const updatemultipleposts = async (postids, updatedata) => { try { const post = parse object extend('post'); const posts = postids map(id => { const post = new post(); post id = id; return post; }); posts foreach(post => { object entries(updatedata) foreach((\[key, value]) => { post set(key, value); }); }); await parse object saveall(posts); console log('posts updated successfully'); } catch (error) { console error('error updating posts ', error); throw error; } }; estrategias de caché implemente caché del lado del cliente para datos que se acceden con frecuencia para reducir las solicitudes a la api uso eficiente de livequery suscríbase solo a los datos necesarios y siempre cancele la suscripción cuando los datos ya no sean necesarios optimización del manejo de archivos redimensione las imágenes antes de subirlas para ahorrar ancho de banda y almacenamiento const uploadresizedimage = async (originalfile, maxwidth = 1200, maxheight = 1200) => { return new promise((resolve, reject) => { try { const reader = new filereader(); reader onload = (event) => { const img = new image(); img onload = () => { let width = img width; let height = img height; if (width > maxwidth) { height = math round(height (maxwidth / width)); width = maxwidth; } if (height > maxheight) { width = math round(width (maxheight / height)); height = maxheight; } const canvas = document createelement('canvas'); canvas width = width; canvas height = height; const ctx = canvas getcontext('2d'); ctx drawimage(img, 0, 0, width, height); canvas toblob(async (blob) => { const resizedfile = new file(\[blob], originalfile name, { type originalfile type, lastmodified date now() }); const parsefile = new parse file(resizedfile name, resizedfile); await parsefile save(); resolve(parsefile); }, originalfile type, 0 8); }; img src = event target result; }; reader readasdataurl(originalfile); } catch (error) { reject(error); } }); }; monitoreo y escalado utiliza las analíticas y registros del panel de back4app para monitorear el uso y rendimiento de la api considera actualizar tu plan o implementar soluciones de sharding y cdn a medida que crece tu base de usuarios conclusión en este tutorial, aprendiste cómo configurar un backend de red social con back4app, cubriendo la autenticación de usuarios, gestión de publicaciones, mensajería en tiempo real, características de búsqueda y técnicas de optimización avanzadas por favor, también lee nuestra publicación en el blog que detalla cómo desarrollar una aplicación de redes sociales ¡feliz codificación y buena suerte con tu backend de red social!