Project Templates
Social Network
Come costruire un backend per un social network?
49 min
introduzione in questa guida, scoprirai come impostare un backend robusto per un social network utilizzando una piattaforma backend come servizio questo backend sarà progettato per gestire l'autenticazione degli utenti, la gestione dei dati, la comunicazione in tempo reale e altro ancora, il tutto garantendo sicurezza ed efficienza alla fine di questo tutorial, il tuo progetto avrà questo aspetto panoramica del social network suggerimento per il codice sorgente completo, visita il nostro repository github su github https //github com/templates back4app/back4gram punti chiave gestione semplificata del backend sfrutta il backend gestito di back4app per semplificare l'autenticazione degli utenti, l'archiviazione dei dati e le funzionalità in tempo reale interazioni in tempo reale utilizza livequery per abilitare la messaggistica dinamica e gli aggiornamenti immediati dei contenuti gestione completa degli utenti e dei contenuti stabilisci sistemi robusti per profili utente, post, commenti e interazioni sociali scalabilità e prestazioni applica ottimizzazione delle query, caching e operazioni in batch per mantenere alte prestazioni man mano che la tua rete cresce integrazioni avanzate migliora il tuo backend con cloud functions, lavori in background e notifiche push per un'esperienza utente più ricca requisiti prima di iniziare, assicurati di avere un account back4app registrati gratuitamente su back4app com https //www back4app com/ node js e npm installa node js (v14 x o superiore) da nodejs org https //nodejs org/ conoscenze di base di javascript e react un editor di codice usa editor come visual studio code o sublime text passo 1 — configurare il tuo backend back4app creare un nuovo progetto su back4app accedi al tuo dashboard di back4app clicca su "crea una nuova app" dai un nome alla tua app (ad esempio, "back4social") e scegli la regione del server più vicina a te una volta creato il progetto, lo vedrai elencato nel tuo dashboard di back4app questo progetto sarà la base per tutte le configurazioni del backend discusse in questo tutorial progettazione dello schema del database il backend del tuo social network utilizzerà le seguenti classi utente (fornito da parse per impostazione predefinita) estendi con campi come bio e immagine del profilo post contiene contenuti testuali e caricamenti di immagini commento memorizza i commenti degli utenti sui post conversazione rappresenta una sessione di chat tra utenti messaggio contiene messaggi individuali in una conversazione stato di digitazione indica quando un utente sta digitando aggiunta di classi al database in back4app, i dati sono memorizzati in classi puoi creare una nuova classe nel dashboard di back4app o tramite un agente ai per creare utilizzando il dashboard, per favore naviga nella sezione “database” nel tuo dashboard di back4app crea una nuova classe e aggiungi colonne pertinenti, come titolo (string) e iscompleted (boolean) il backend della tua rete sociale utilizzerà le seguenti classi per impostare il tuo database vai alla "database" sezione nel tuo dashboard di back4app migliora la classe user apri la classe esistente utente aggiungi colonne biografia (string) avatar (file) seguaci (numero, predefinito 0) seguente (numero, predefinito 0) crea la classe post clicca "crea una classe" chiamalo post e aggiungi contenuto (stringa) autore (puntatore a utente ) immagine (file) mi piace (numero, predefinito 0) piaciutoda (array) crea la classe commento chiamalo commento e includi contenuto (stringa) autore (puntatore a utente ) post (puntatore al post) crea la classe conversation chiamalo conversazione e aggiungi partecipanti (array) ultimomessaggio (stringa) crea la classe messaggio chiamalo messaggio e aggiungi testo (string) mittente (puntatore a utente ) conversazione (puntatore alla conversazione) crea la classe typingstatus chiamalo typingstatus e aggiungi utente (puntatore a utente ) conversazione (puntatore alla conversazione) stascrivendo (booleano) per creare utilizzando l'agente ai, per favore apri l'agente ai dal tuo dashboard app o dal menu descrivi il tuo modello di dati in linguaggio semplice lascia che l'agente ai crei automaticamente lo schema configurazione delle autorizzazioni della classe per proteggere i tuoi dati, regola le autorizzazioni a livello di classe (clp) vai a "sicurezza e chiavi" nel tuo cruscotto sotto "sicurezza a livello di classe" , imposta le regole di lettura/scrittura ad esempio, consenti l'accesso pubblico in lettura per post mentre restringi le operazioni di aggiornamento/cancellazione all'autore del post abilitare le funzionalità in tempo reale con livequery in "impostazioni del server" , trova parse server e attiva livequery includi queste classi per il monitoraggio in tempo reale messaggio stato di digitazione post (per aggiornare i mi piace e i commenti in tempo reale) impostazione livequery recupera le chiavi della tua applicazione naviga su "impostazioni app" > "sicurezza e chiavi" salva il tuo id applicazione , chiave javascript , url del server , e url del server livequery chiavi di sicurezza passo 2 — collegare il tuo frontend con back4app impostazione delle variabili d'ambiente crea un env local file nella directory principale del tuo progetto 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 sostituisci i segnaposto con le tue credenziali reali configurazione del parse sdk crea un file di configurazione per inizializzare 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; poi importa questo file nel punto di ingresso della tua app (ad es , 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 — configurazione dell'autenticazione utente il server parse di back4app offre un sistema di gestione degli utenti integrato tramite la parse user classe come parse gestisce gli utenti gestisce le credenziali degli utenti (nome utente, email, password) gestisce lo stato di autenticazione e i token di sessione semplifica i processi di registrazione e accesso esempio di registrazione utente // 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'}); } } }; esempio di accesso utente // 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); } }; controllo di una sessione esistente 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; } }; implementazione del ripristino della password 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 — gestione dei post creazione di nuovi post 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; } }; recuperare i post 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; } }; implementazione della funzione mi piace/non mi piace 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 — abilitare i commenti sui post aggiungere un commento 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; } }; recuperare i commenti per un post specifico 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 — gestire i profili utente recuperare un profilo utente 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; } }; aggiornare un profilo utente 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 — implementare la messaggistica in tempo reale la funzione livequery di back4app consente la messaggistica in tempo reale e indicatori di digitazione creare una conversazione 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; } }; inviare un messaggio 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; } }; iscriversi agli aggiornamenti dei messaggi tramite 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); } }; implementazione degli indicatori di digitazione 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 — aggiunta di funzionalità di ricerca ricerca utenti 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; } }; ricerca post 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; } }; ricerca 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 — sfruttare le funzionalità avanzate di back4app back4app offre strumenti avanzati come cloud functions, lavori in background e hook per arricchire il tuo backend cloud functions per la logica lato server // 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 }); }); ricerca avanzata 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; }); lavori in background per attività ricorrenti 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"; }); funzionalità avanzate aggiuntive notifiche push utilizza il servizio di push di back4app per avvisare gli utenti riguardo ai nuovi messaggi controllo degli accessi basato sui ruoli definisci i ruoli per gestire i permessi per azioni come la moderazione dei contenuti webhook integra servizi esterni attivando webhook dal cloud code passo 10 — ottimizzazione delle prestazioni man mano che il tuo backend cresce, la messa a punto delle prestazioni è fondamentale considera le seguenti tecniche indicizzazione del database crea indici sui campi frequentemente interrogati (ad es , autore , creatoil , nomeutente ) per migliorare le prestazioni delle query ottimizzazione delle query ottimizza le query selezionando solo i campi necessari e utilizzando la paginazione per limitare i set di risultati operazioni in batch utilizza aggiornamenti e cancellazioni in batch per ridurre al minimo le chiamate 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; } }; strategie di caching implementa il caching lato client per i dati che vengono frequentemente accessi per ridurre le richieste api utilizzo efficiente di livequery iscriviti solo ai dati necessari e disiscriviti sempre quando i dati non sono più necessari ottimizzazione della gestione dei file ridimensiona le immagini prima del caricamento per risparmiare larghezza di banda e spazio di archiviazione 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); } }); }; monitoraggio e scalabilità utilizza le analisi e i log del dashboard di back4app per monitorare l'uso e le prestazioni dell'api considera di aggiornare il tuo piano o di implementare soluzioni di sharding e cdn man mano che la tua base utenti cresce conclusione in questo tutorial, hai imparato come impostare un backend per un social network con back4app, coprendo l'autenticazione degli utenti, la gestione dei post, la messaggistica in tempo reale, le funzionalità di ricerca e le tecniche di ottimizzazione avanzate ti preghiamo di leggere anche il nostro post sul blog che dettaglia come sviluppare un'app per social media buon coding e buona fortuna con il tuo backend per social network!