Project Templates
Social Network
Como construir um backend para uma rede social?
49 min
introdução neste guia, você descobrirá como configurar um backend robusto para uma rede social usando uma plataforma de backend como serviço este backend será projetado para gerenciar autenticação de usuários, gerenciamento de dados, comunicação em tempo real e mais—tudo isso garantindo segurança e eficiência ao final deste tutorial, seu projeto terá esta aparência visão geral da rede social dica para o código fonte completo, visite nosso repositório no github em github https //github com/templates back4app/back4gram principais conclusões gerenciamento de backend simplificado aproveite o backend gerenciado da back4app para simplificar a autenticação de usuários, armazenamento de dados e funcionalidades em tempo real interações em tempo real utilize livequery para habilitar mensagens dinâmicas e atualizações imediatas de conteúdo gerenciamento abrangente de usuários e conteúdo estabeleça sistemas robustos para perfis de usuários, postagens, comentários e interações sociais escalabilidade e desempenho aplique otimização de consultas, cache e operações em lote para manter alto desempenho à medida que sua rede cresce integrações avançadas melhore seu backend com funções em nuvem, trabalhos em segundo plano e notificações push para uma experiência de usuário mais rica pré requisitos antes de começar, certifique se de que você tem uma conta back4app registre se gratuitamente em back4app com https //www back4app com/ node js e npm instale o node js (v14 x ou mais recente) a partir de nodejs org https //nodejs org/ conhecimento básico de javascript e react um editor de código use editores como visual studio code ou sublime text passo 1 — configurando seu backend back4app criando um novo projeto no back4app faça login no seu painel do back4app clique em "criar um novo aplicativo" nomeie seu aplicativo (por exemplo, "back4social") e escolha a região do servidor mais próxima de você uma vez que o projeto é criado, você o verá listado no seu painel do back4app este projeto será a base para todas as configurações de backend discutidas neste tutorial projetando o esquema do banco de dados seu backend de rede social usará as seguintes classes usuário (fornecido pelo parse por padrão) estender com campos como bio e foto de perfil postagem contém conteúdo de texto e uploads de imagem comentário armazena comentários de usuários em postagens conversa representa uma sessão de chat entre usuários mensagem contém mensagens individuais em uma conversa statusdedigitação indica quando um usuário está digitando adicionando classes de banco de dados no back4app, os dados são armazenados em classes você pode criar uma nova classe no painel do back4app ou via um agente de ia para criar usando o painel, por favor navegue até a seção “banco de dados” no seu painel do back4app crie uma nova classe e adicione colunas relevantes, como título (string) e iscompleted (boolean) seu backend de rede social usará as seguintes classes para configurar seu banco de dados vá para o "banco de dados" seção no seu painel do back4app aprimorar a classe user abra a existente usuário classe adicionar colunas bio (string) avatar (arquivo) seguidores (número, padrão 0) seguindo (número, padrão 0) criar a classe post clique "criar uma classe" nomeie o post e adicione conteúdo (string) autor (ponteiro para usuário ) imagem (arquivo) gosta (número, padrão 0) curtidopor (array) crie a classe comment nomeie o comentário e inclua conteúdo (string) autor (ponteiro para usuário ) post (ponteiro para post) crie a classe conversation nomeie o conversa e adicione participantes (array) últimamensagem (string) criar a classe mensagem nomeie o mensagem e adicione texto (string) remetente (ponteiro para usuário ) conversa (ponte para conversa) crie a classe typingstatus nomeie o typingstatus e adicione usuário (ponteiro para usuário ) conversa (ponte para conversa) estádigitando (booleano) para criar usando o agente de ia, por favor abra o agente de ia no seu painel de aplicativos ou no menu descreva seu modelo de dados em linguagem simples deixe o agente de ia criar o esquema automaticamente configurando permissões de classe para proteger seus dados, ajuste as permissões de nível de classe (clps) vá para "segurança e chaves" no seu painel sob "segurança de nível de classe" , defina regras de leitura/gravação por exemplo, permita acesso público de leitura para postagem enquanto restringe operações de atualização/exclusão ao autor da postagem habilitando recursos em tempo real com livequery em "configurações do servidor" , localize parse server e ative livequery inclua estas classes para monitoramento em tempo real mensagem status de digitação postagem (para atualizar curtidas e comentários em tempo real) configuração do livequery recupere suas chaves de aplicativo navegue até "configurações do app" > "segurança & chaves" salve seu id do aplicativo , chave javascript , url do servidor , e url do servidor livequery chaves de segurança passo 2 — vinculando seu frontend com back4app configurando variáveis de ambiente crie um env local arquivo no diretório raiz do seu projeto 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 substitua os espaços reservados pelas suas credenciais reais configurando o sdk do parse crie um arquivo de configuração para inicializar o 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; em seguida, importe este arquivo no ponto de entrada do seu aplicativo (por exemplo, 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> ); passo 3 — configurando a autenticação do usuário o parse server do back4app oferece um sistema de gerenciamento de usuários embutido através da parse user classe como o parse gerencia usuários gerencia credenciais de usuário (nome de usuário, e mail, senha) gerencia estado de autenticação e tokens de sessão simplifica os processos de registro e login exemplo de registro de usuário // 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'}); } } }; exemplo de login de usuário // 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 uma sessão 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; } }; implementação de redefinição de senha 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); } }; passo 4 — lidando com postagens criando novas postagens 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; } }; recuperando postagens 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 o recurso curtir/descurtir 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; } }; passo 5 — habilitando comentários em postagens adicionando um comentário 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 comentários para um post específico 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; } }; passo 6 — gerenciando perfis de usuário recuperando um perfil de usuário 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; } }; atualizando um perfil de usuário 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; } }; passo 7 — implementando mensagens em tempo real o recurso livequery do back4app permite mensagens em tempo real e indicadores de digitação criando uma conversa 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 uma mensagem 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; } }; inscrevendo se para atualizações de mensagens via 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 digitação 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; } }; passo 8 — adicionando capacidades de pesquisa pesquisa de usuário 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; } }; pesquisa de postagem 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; } }; pesquisa de hashtag 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; } }; passo 9 — aproveitando recursos avançados do back4app back4app oferece ferramentas avançadas como funções em nuvem, trabalhos em segundo plano e hooks para enriquecer seu backend funções em nuvem para lógica do lado do 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 }); }); busca avançada com código em nuvem 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; }); trabalhos em segundo plano para tarefas recorrentes 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"; }); recursos adicionais avançados notificações push use o serviço de push do back4app para alertar os usuários sobre novas mensagens controle de acesso baseado em funções defina funções para gerenciar permissões para ações como moderação de conteúdo webhooks integre serviços externos acionando webhooks a partir do cloud code passo 10 — otimização de desempenho à medida que seu backend escala, a afinação de desempenho é fundamental considere as seguintes técnicas indexação de banco de dados crie índices em campos frequentemente consultados (por exemplo, autor , criadoem , nomedeusuario ) para aumentar o desempenho das consultas otimização de consultas otimize consultas selecionando apenas os campos necessários e usando paginação para limitar os conjuntos de resultados operações em lote utilize atualizações em lote e exclusões para minimizar chamadas de 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; } }; estratégias de cache implemente cache do lado do cliente para dados que são acessados com frequência para reduzir solicitações de api uso eficiente de livequery inscreva se apenas nos dados necessários e sempre cancele a inscrição quando os dados não forem mais necessários otimização de manipulação de arquivos redimensione imagens antes do upload para economizar largura de banda e armazenamento 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); } }); }; monitoramento e escalonamento use as análises e logs do painel do back4app para monitorar o uso e o desempenho da api considere atualizar seu plano ou implementar soluções de sharding e cdn à medida que sua base de usuários cresce conclusão neste tutorial, você aprendeu como configurar um backend de rede social com o back4app, cobrindo autenticação de usuários, gerenciamento de postagens, mensagens em tempo real, recursos de busca e técnicas avançadas de otimização por favor, leia também nosso post no blog detalhando como desenvolver um aplicativo de mídia social feliz codificação e boa sorte com seu backend de rede social!