Build a Decentralized Social Network with Reactjs, TailwindCSS & Lens Protocol

Software Engineer | Developer Advocate | Technical Writer | Content Creator
Search for a command to run...

Software Engineer | Developer Advocate | Technical Writer | Content Creator
No comments yet. Be the first to comment.
I'll be sharing amazing content and resources on web3, how to build and deploy smart contracts, blockchain, and more in this series.
The advent of the metaverse and acceptance of blockchain, decentralized finance, and crypto by the general public continues to generate headlines, attracting developers to the web3 ecosystem. However, everyone wants to know more about Web3, which lea...
Step-by-Step Solidity Guide for Beginners: Building Your First Token

Prioritizing Depth Instead of Going with Trends

Cross-chain messaging allows seamless communication and interaction between different blockchain ecosystems, from Ethereum Virtual Machine (EVM)-based chains to Cosmos-based chains. Axelar has long been the best way to connect EVM and Cosmos chains v...

You have millions of unique images in your gallery, each representing a piece of private or personal data stored on your phone. After an incident, you lost your device, but it was returned to you after many months, with claims that the images hadn't ...

Marking a Year of Contributions to Axelar: Pioneering the Future of Interoperability

One of the most important advantages of social networks is the potential for real human connections. Websites and applications that emphasize collaboration, sharing of content, engagement, and community-based feedback are collectively referred to as social media.
Web3 social appears to have enormous potential for consumers, leveraging Lens Protocol to restore faith in what social media may be while also giving us the power to manage how our content is utilized.
This post will teach us how to build a decentralized social network with Reactjs, Tailwindcss, and Lens protocol.
Live Project: Decentralized Social Network
GitHub Repository: Decentralized Social Network GitHub
Make sure to have Node.js or npm installed on your computer. If you don't, click here.
To quickly get started with the project setup and installation, we will clone this project on GitHub and ensure we are on the starter branch.
Next, we will launch the project locally after cloning it using the following command on our terminal.
Project installation using yarn.
cd decentralize-social-media-app-with-lens-protocol && yarn && yarn start
Project installation using npm.
cd decentralize-social-media-app-with-lens-protocol && npm install && npm start
After cloning and installing the project, we should have something similar to what we have below:

Before implementing all profile retrieval, let's discuss the Lens Protocol in the following section.
The Lens Protocol is a smart contract-based social graph on the Polygon Proof-of-Stake blockchain. It enables developers to build composable, user-owned social graphs by allowing them to own the links between themselves and their community.
Learn more about Lens protocol.
In this section, we will build a decentralized social network that implements all profile and single profile retrieval and publications.
After cloning the repository successfully, we can now build the feature to fetch all profiles using the Lens protocol API.
To set up a graphQL call to the Lens protocol API, we can now go to the api.js file inside the pages/api/ directory and edit it with the following code snippet.
import { createClient } from "urql";
const API_URL = "https://api.lens.dev";
// Setup the client to use the API_URL as the base URL
export const client = createClient({
url: API_URL,
});
// Get the recommended profiles
export const getProfiles = `
query RecommendedProfiles {
recommendedProfiles {
id
name
bio
attributes {
displayType
traitType
key
value
}
followNftAddress
metadata
isDefault
picture {
... on NftImage {
contractAddress
tokenId
uri
verified
}
... on MediaSet {
original {
url
mimeType
}
}
__typename
}
handle
coverPicture {
... on NftImage {
contractAddress
tokenId
uri
verified
}
... on MediaSet {
original {
url
mimeType
}
}
__typename
}
ownedBy
dispatcher {
address
canUseRelay
}
stats {
totalFollowers
totalFollowing
totalPosts
totalComments
totalMirrors
totalPublications
totalCollects
}
followModule {
... on FeeFollowModuleSettings {
type
amount {
asset {
symbol
name
decimals
address
}
value
}
recipient
}
... on ProfileFollowModuleSettings {
type
}
... on RevertFollowModuleSettings {
type
}
}
}
}
`;
In the code snippet above, we:
createClient from urql to help send GraphQL requests to our APIgetProfilesendpoint to help retrieve recommended profiles. To learn more, check the official Lens protocol documentationNext, we will import the client and getProfiles in the index.js file and update the file with the following code snippet.
import Head from "next/head";
import Profiles from "../components/profiles";
import { useState, useEffect } from "react";
import { client, getProfiles } from "../pages/api/api";
export default function Home() {
// Setup the state for the profiles
const [profiles, setProfiles] = useState([]);
// Get the recommended profiles
const fetchProfiles = async () => {
try {
const response = await client.query(getProfiles).toPromise();
setProfiles(response.data.recommendedProfiles);
console.log(response.data.recommendedProfiles);
} catch (e) {
console.log(e);
}
};
// Run the fetchProfiles function when the component is mounted
useEffect(() => {
fetchProfiles();
}, []);
// Render the component
return (
<div className="grid grid-cols-3 divide-x">
<Head>
<title>Decentralize Social Media App - Lens protocol</title>
<meta name="description" content="Decentralize Social Media App" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="col-span-3">
<div className="px-4 py-8">
<h1 className="text-3xl font-bold leading-tight text-center">
Decentralize Social Media App - Lens protocol
</h1>
<p className="text-center">
This is a decentralized social media app built on the Lens protocol.
</p>
</div>
</div>
{profiles && profiles.length > 0 ? (
<Profiles profiles={profiles} />
) : (
<div className="text-center text-gray-500 p-5 font-medium text-xl tracking-tight leading-tight">
<p>Loading...</p>
</div>
)}
</div>
);
}
In the code snippet above, we:
profiles to save the profile retrieved from the API requestfetchProfiles to get recommended profiles from the API using the client we created earlier and the recommended profile request endpointfetchProfiles function when the component is mountedprofiles variable length is greater than 0, we then render the Profiles component we imported in the earlier step; otherwise, we return the Loading... while the API request is in progressAt the moment, the Profiles component is empty. Let us work on that in the next step.
Inside the profiles.js file in the components directory, we will update it with the following code snippet to iterate over profile records and retrieve and render individual profiles in a card layout.
import React from "react";
import Image from "next/image";
import Link from "next/link";
export default function Profiles({ profiles }) {
return (
<>
{profiles.length > 0 &&
profiles.map((profile, index) => (
<Link href={`/profile/${profile.id}`} key={index}>
<div className="max-w-md rounded-lg shadow-lg bg-white mt-5 mb-5 p-5 border border-radius-8 cursor-pointer hover:bg-gray-100 hover:shadow-lg ml-8">
<div className="flex flex-shrink-0 p-4 pb-0">
<div className="flex items-center">
<div>
{profile &&
profile.picture &&
profile.picture.original &&
profile.picture.original.url ? (
<Image
src={`${profile.picture.original.url}`}
alt={profile.name}
width={64}
height={64}
className="rounded-full"
/>
) : (
<div className="rounded-full bg-gray-500 h-12 w-12"></div>
)}
</div>
<div className="ml-3">
<p className="text-base leading-6 font-medium ">
{profile.name}{" "}
<span className="text-sm leading-5 font-medium text-gray-500 group-hover:text-gray-400 transition ease-in-out duration-150">
@{profile.handle}
</span>
</p>
</div>
</div>
</div>
<div className="pl-16">
<p className="text-base width-auto font-small flex-shrink">
{profile.bio ? profile.bio : "No bio available 😢"}
</p>
</div>
</div>
</Link>
))}
</>
);
}
Next, we will start the server in development mode using the command below;
yarn dev
When we open our browser, we should see something similar to what is shown below.

Using the Lens protocol API, we will create a single profile retrieval functionality in this section. As we may have seen, when we click on any card, we see a 404 page, which means the route does not exist, but let's rectify that immediately.
Let's update the api.js in the pages/api/ directory with the following code snippet.
//...
export const getProfile = `
query Profiles($id: ProfileId!) {
profiles(request: { profileIds: [$id], limit: 25 }) {
items {
id
name
bio
attributes {
displayType
traitType
key
value
}
metadata
isDefault
picture {
... on NftImage {
contractAddress
tokenId
uri
verified
}
... on MediaSet {
original {
url
mimeType
}
}
__typename
}
handle
coverPicture {
... on NftImage {
contractAddress
tokenId
uri
verified
}
... on MediaSet {
original {
url
mimeType
}
}
__typename
}
ownedBy
dispatcher {
address
canUseRelay
}
stats {
totalFollowers
totalFollowing
totalPosts
totalComments
totalMirrors
totalPublications
totalCollects
}
}
pageInfo {
prev
next
totalCount
}
}
}
`;
In the code snippet above, we added the endpoint to get a single profile using the provided by the Lens protocol in their docs.
Next, we will update the [id].js file in the profile folder under the pages directory with the following code snippet.
In this code snippet above, we:
client and getProfile from the api.js fileprofile to save the profile retrieveduseRouter hook to access the profile id. fetchProfile to retrieve a single profile using the client and API endpoint useEffect hookLet's head to our browser to view what we have. We should have something similar to what is shown below.

As highlighted in the image above, those are dynamic data retrieved from the API associated with the account.
Next, we will implement fetching profile publications in the following section.
A profile's posts, comments, and mirrors are considered publications. Learn more about the publication's endpoints here.
Let's update the api.js in the pages/api/ directory with the following code snippet.
//...
export const getPublications = `
query Publications($id: ProfileId!, $limit: LimitScalar) {
publications(request: {
profileId: $id,
publicationTypes: [POST],
limit: $limit
}) {
items {
__typename
... on Post {
...PostFields
}
}
}
}
fragment PostFields on Post {
id
metadata {
...MetadataOutputFields
}
createdAt
}
fragment MetadataOutputFields on MetadataOutput {
name
description
content
media {
original {
...MediaFields
}
}
attributes {
displayType
traitType
value
}
}
fragment MediaFields on Media {
url
mimeType
}
`;
Next, we will update the [id].js file in the profile folder under the pages directory with the following code snippet to retrieve profile publications.
//...
import { client, getProfile, getPublications } from "../api/api";
export default function Profile() {
//...
const [publications, setPublications] = useState();
async function getProfilePublications() {
try {
const returnedPublications = await client
.query(getPublications, { id, limit: 10 })
.toPromise();
const publicationsData = returnedPublications.data.publications.items;
console.log("publicationsData", publicationsData);
setPublications(publicationsData);
} catch (err) {
console.log("error fetching publications...", err);
}
}
useEffect(() => {
if (id) {
//...
getProfilePublications();
}
}, [id]);
return (
<div>
//...
<div className="container mx-auto flex flex-col lg:flex-row mt-3 text-sm leading-normal">
//...
<div className="w-full lg:w-1/2 bg-white mb-4">
<div className="p-3 text-lg font-bold border-b border-solid border-grey-light">
Publications
</div>
{publications &&
publications.map((publication) => (
<div key={publication.id}>
<div className="flex border-b border-solid border-grey-light">
<div className="w-1/8 text-right pl-3 pt-3">
<div>
<i className="fa fa-thumb-tack text-teal mr-2"></i>
</div>
<div>
<a href="#">
{profile &&
profile.picture &&
profile.picture.original &&
profile.picture.original.url ? (
<Image
src={profile.picture.original.url}
alt="avatar"
className="rounded-full h-12 w-12 mr-2"
width={50}
height={50}
/>
) : (
<div className="rounded-full bg-gray-500 h-12 w-12"></div>
)}
</a>
</div>
</div>
<div className="w-7/8 p-3 pl-0 ml-4">
<div className="flex justify-between">
<div>
<span className="font-bold">
<a href="#" className="text-black">
{profile && profile.name}
</a>
</span>
<span className="text-grey-dark">
@{profile && profile.handle}
</span>
<span className="text-grey-dark">·</span>
</div>
</div>
<div className="mb-4">
<p className="mb-6 text-grey-dark mt-2">
{publication.metadata.content}
</p>
<p>
<a href="#">
{publication.metadata.media > 0 &&
publication.metadata.media[0].original.mimetype ===
"video/mp4" ? (
<video
controls
style={{ width: "700", height: "400" }}
>
<source
src={publication.metadata.media[0].original.url}
type="video/mp4"
/>
</video>
) : null}
</a>
</p>
</div>
</div>
</div>
</div>
))}
</div>
//...
</div>
</div>
);
}
In this code snippet above, we:
getPublications from the api.js filepublications to save the publications retrievedgetProfilePublications to retrieve profile publication using the client and API endpoint useEffect hookNext, we can test our application by navigating to our browser.

This post covers a few endpoints provided by Lens protocol. We can always play around with endpoints and functionality like Follow, Create Profile, Comment, Mirror, etc.
I strongly advise looking over the Lens API documentation and the Lens apps for some ideas.
This post teaches us how to build a decentralized social network with Reactjs, TailwindCSS & Lens protocol.
I'd love to connect with you at Twitter | LinkedIn | GitHub | Portfolio
See you in my next blog article. Take care!!!