Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Ā·

12 min read

Decentralized applications (dApps) are one of the most promising applications of blockchain technology. They open up new possibilities for consumer and business-focused products with never-before-seen capabilities.

It's fascinating to see how powerful decentralized applications may be built to supplement the commercial environment.

This post will teach us how to Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS, and Solidity.

It will be a platform where anyone on the internet can read, share, and post news, with the data being stored on the Polygon network's blockchain using smart contracts.

A smart contract is code stored on the blockchain and can be read and written from; we'll get into more detail later.

We'll build and deploy the smart contract and a website that allows people to connect their wallets and interact with our smart contract.

šŸ‘‰ GitHub Repositories

Prerequisite

Let us ensure we have Node/NPM installed on our PC. If we don't have it installed, head over here for a guide.

Project Setup and Installation

Let's navigate to the terminal. We'll need to cd into any directory of our choice and then run the following commands:

mkdir newsfeed-be
cd newsfeed-be
npm init -y
npm install --save-dev hardhat

Let's get a sample project by running the command below:

npx hardhat

We'll go with the following options:

  • A sample project.

  • Accept all other requests.

Installing hardhat-waffle and hardhat-ethers is required for the sample project.

Just in case it didn't install automatically, we will install this other requirement with the following command:

npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts

Next, we will install @openzeppelin/contracts for the counter we will use later in this tutorial.

npm i @openzeppelin/contracts

To make sure everything is working, let us run the command below.

npx hardhat test

We will see a passed test result in our console.

It is now possible for us to delete sample-test.js from the test folder and delete sample-script.js from the scripts directory. After that, go to contracts and delete Greeter.sol.

The folders themselves should not be deleted!

We'll create a NewsFeed.sol file inside the contracts directory. When using Hardhat, file layout is crucial, so pay attention! We're going to start with the basic structure of every contract.

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract NewsFeed {

     constructor() {
        console.log("NewsFeed deployed");
    }
}

To build and deploy our smart contract, we will navigate to the scripts folder, create a new run.js file, and update it with the following code snippet:

const main = async () => {
  // This will actually compile our contract and generate the necessary files we need to work with our contract under the artifacts directory.
  const newsFeedContractFactory = await hre.ethers.getContractFactory(
    "NewsFeed"
  );
  const newsFeedContract = await newsFeedContractFactory.deploy();

  await newsFeedContract.deployed(); // We'll wait until our contract is officially deployed to our local blockchain! Our constructor runs when we deploy.

  console.log("NewsFeed Contract deployed to: ", newsFeedContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

Let's run it with the following command.

npx hardhat run scripts/run.js

You should see something similar to what we have below:

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We have a working smart contract šŸ„³ Let us deploy it to a local network.

Under the scripts folder, we will create a deploy.js file. Add the following code snippet.

const main = async () => {
  const [deployer] = await hre.ethers.getSigners();
  const accountBalance = await deployer.getBalance();

  console.log("Deploying contracts with account: ", deployer.address);
  console.log("Account balance: ", accountBalance.toString());

  const Token = await hre.ethers.getContractFactory("NewsFeed");
  const portal = await Token.deploy();
  await portal.deployed();

  console.log("NewsFeed address: ", portal.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();

Before deploying, let us ensure our local node is up and running in a separate terminal with the following command.

npx hardhat node

Next, we will deploy our smart contract.

npx hardhat run scripts/deploy.js --network localhost

We should have something like this.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Building and Deploying NewsFeed Smart Contract to Blockchain

Everything, including the test script and the deploy.js file, is in place. We'll update the smart contract, run.js, and deploy.js files with the following code snippet:

Updating the contracts/NewsFeed.sol file.

Update scripts/run.js

const main = async () => {
  // This will actually compile our contract and generate the necessary files we need to work with our contract under the artifacts directory.
  const newsFeedContractFactory = await hre.ethers.getContractFactory(
    "NewsFeed"
  );
  const newsFeedContract = await newsFeedContractFactory.deploy();

  await newsFeedContract.deployed(); // We'll wait until our contract is officially deployed to our local blockchain! Our constructor runs when we deploy.

  console.log("NewsFeed Contract deployed to: ", newsFeedContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

scripts/deploy.js

const main = async () => {
  const [deployer] = await hre.ethers.getSigners();
  const accountBalance = await deployer.getBalance();

  console.log("Deploying contracts with account: ", deployer.address);
  console.log("Account balance: ", accountBalance.toString());

  const Token = await hre.ethers.getContractFactory("NewsFeed");
  const portal = await Token.deploy();
  await portal.deployed();

  console.log("NewsFeed address: ", portal.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();

It's finally time to get down to business and deploy to the blockchain.

Before deploying to the blockchain, we'll need to create an Alchemy account.

Alchemy enables us to broadcast our contract creation transaction so that miners can pick it up as quickly as feasible. Once mined, the transaction is published as a valid transaction to the blockchain. After that, everyone's blockchain copy is updated.

After you sign up, we'll create an app like the one below. Remember to switch the network to Mumbai, where we'll be deploying.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We will need to grab our keys, as shown below, and store them for later use:

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We'll need some MATIC tokens in our testnet account, and we'll have to request some from the network. Polygon Mumbai can get some phony MATIC by using a faucet. This fake MATIC can only be used on this testnet.

We can grab some MATIC token here

Let us update the hardhat.config.js file in the root project directory.

require("@nomiclabs/hardhat-waffle");
require("dotenv").config();

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.4",
  networks: {
    mumbai: {
      url: process.env.STAGING_ALCHEMY_KEY,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

If we look at the code snippet above, we can see that some keys were read from the .env file, as well as the import at the top of require("dotenv").config(), which implies we'll need to install the dotenv package and also create a .env file using the command below:

npm install -D dotenv

touch .env

Inside the .env file, we will add the following keys:

STAGING_ALCHEMY_KEY= // Add the key we copied from the Alchemy dashboard here
PRIVATE_KEY= // Add your account private key here

Getting our private account key is easy. Check out this post.

Next, let's write a basic test to test the most critical functions we'll use.

To do so, open we will create a feed-test.js file inside the test directory and update it with the following code:

Next, we will run the test with the following command:

npx hardhat test

Now we can run the command to deploy our contract to a real blockchain network.

npx hardhat run scripts/deploy.js --network mumbai

Our output should look like what we have below.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We just deployed our contract. šŸ„³šŸ„³šŸ„³

Frontend React Client

To quickly get started with the project setup and installation, we will clone this project on GitHub and ensure we are on the project-setup branch.

Next, we will launch the project locally after cloning it using the following command on our terminal.

cd newsfeed-fe && yarn && yarn start

Or

cd newsfeed-fe && npm install && npm start

After cloning and installing the project, we should have something similar to what we have below:

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We want to get all the news feeds from the smart contract we just launched without requiring users to connect their wallets. This implies that anyone can use our app to browse information without linking their wallets and only connect wallets when they wish to create a news feed.

Let us update the getContract.js file inside the utilities folder with the following code snippet.

import ContractAbi from "./newsFeed.json";
import { ethers } from "ethers";

export default function getContract() {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner(
    "0x2C08B4B909F02EA5D8A0E44986720D76BAC8224B" // Random (fake) wallet address
  );
  let contract = new ethers.Contract(
    "0x545ed82953b300ae5a8b21339c942788599Cd239", // Our contract adress
    ContractAbi.abi,
    signer
  );
  return contract;
}

In the code snippet above, we get our contract and include a random wallet address in the getSigner function. This is because we want everyone that visits our site to reads news without having to connect their wallets.

Ensure passing a valid ethereum wallet address when creating/saving a record on the blockchain

We also added a contract address displayed in our terminal when we deployed our contract to the blockchain.

Let's go back to the smart contract project we worked on before, then navigate to artifacts/contracts/NewsFeed.json and copy the entire content inside it. We will update the newsfeed.json file in the utilities folder with what we copied.

Building the FeedList Component

In this section, we will create a FeedList.js file inside the component folder and update it with the following code snippet.

Next, we will import the FeedList component, the toast response, and ToastContainer by updating the HomePage.js file with the following code snippet.

Because no record has yet been recorded on the blockchain, and we are yet to create the function that retrieves all the feeds made, we should have something similar to what is displayed above, which appears empty.

Building User's Connect Wallet Functionality

This section will build the functionality that allows users to contact their wallets on our platform to create a feed.

Let's update the HomePage.js with the following code snippet.

Next, we will update the Header.js file.

Clicking on the Connect your Wallet button, we will get a metamask login popup.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

After connecting, we will be redirected back to our application where the button showing Connect your wallet earlier now shows Create a Feed as shown below.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Building Upload News Feed Page

We will build a page where users can enter new feed details and upload them to the blockchain. Let us create UploadPage.js inside the src directory and update it with the following code snippet.

Next, we will update the App.js file by importing the new page we created with the following code snippet.

//...

function App() {
  return (
    <Routes>
      //...
      <Route path="/upload" element={<Upload />} />
    </Routes>
  );
}

export default App;

Clicking on the Create a New Feed button on the homepage will redirect us to the upload page, as shown below.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

After entering all the required details for upload, we can proceed to submit the feed.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity


Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity


Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

We were redirected to the homepage, and nothing happened :(.

We will create a getFeeds function on the homepage to retrieve all the feeds.

HomePage.js

//...

export default function Main() {

  //...

  /*
   * Get Feeds
   */
  const getFeeds = async () => {
    try {
      setLoading(true);
      const contract = await getContract();
      const AllFeeds = await contract.getAllFeeds();
      /*
       * We only need a title, category, coverImageHash, and author
       * pick those out
       */
      const formattedFeed = AllFeeds.map((feed) => {
        return {
          id: feed.id,
          title: feed.title,
          category: feed.category,
          coverImageHash: feed.coverImageHash,
          author: feed.author,
          date: new Date(feed.date * 1000),
        };
      });
      setFeeds(formattedFeed);
      setLoading(false);
    } catch (err) {
      error(`${err.message}`);
    }
  };

  /*
   * This runs our function when the page loads.
   */
  useEffect(() => {
    getFeeds();

    //...

  }, []);

  return (
    //...
  );
}

const Loader = () => {
  //...
};

Let's wait for the transaction to confirm. It takes a few seconds, and we should see it appear in real-time.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Building the Feed Page

Start by creating the Feed.js file inside the components folder and updating it with the following code snippet.

import React from "react";
import { BiCheck } from "react-icons/bi";
import {
  AiFillTwitterCircle,
  AiFillLinkedin,
  AiFillRedditCircle,
} from "react-icons/ai";

export default function Feed({ feed }) {
  return (
    <div>
      <img
        className=" rounded-lg w-full bg-contain h-80"
        src={`https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
        alt="cover"
      />
      <div className="flex justify-between flex-row py-4 border-borderWhiteGray dark:border-borderGray border-b-2">
        <div>
          <h3 className="text-2xl dark:text-white">{feed.title}</h3>
          <p className="text-gray-500 mt-4">
            {feed.category} ā€¢ {feed.date}
          </p>
        </div>
        <div className="flex flex-row items-center">
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-blue-600 mr-6 text-blue-600 hover:bg-blue-600 hover:text-white"
            href={`https://twitter.com/intent/tweet?text=${feed.title}&url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillTwitterCircle />
          </a>
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-blue-600 mr-6 text-blue-500 hover:bg-blue-600 hover:text-white"
            href={`https://www.linkedin.com/shareArticle?mini=true&url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}&title=${feed.title}&summary=${feed.description}&source=https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillLinkedin />
          </a>
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-red-600 mr-6 text-red-600 hover:bg-red-600 hover:text-white"
            href={`https://www.reddit.com/submit?url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}&title=${feed.title}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillRedditCircle />
          </a>
        </div>
      </div>

      <div className="flex mt-5 flex-row items-center ">
        <div className="flex items-center text-textSubTitle mt-1">
          Author: {feed?.author?.slice(0, 12)}...
          <BiCheck size="20px" color="green" className="ml-1" />
        </div>
      </div>
      <p className="text-sm text-black mt-4">{feed.description}</p>
    </div>
  );
}

Next, we will create the FeedPage.js file inside the src directory and update it with the code snippet below.

In the snippet above, we retrieve a single feed and get related feeds in the feed category.

Next, we will update App.js with the following code snippet.

//...

import Feed from "./FeedPage";

function App() {
  return (
    <Routes>
      //...
      <Route path="/feed" element={<Feed />} />
    </Routes>
  );
}

export default App;

Testing our Application šŸ„³

I have created several posts to test the application, as shown below.

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Single Feed Page

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

Social Share

Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity

After upload, we might experience some delay or glitch when the feed appears on the homepage. We need to wait for some extra minutes, and then everything will become stable ;)

Conclusion

This article teaches us to build a decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity on Polygon Network.

References

I'd love to connect with you on Twitter | LinkedIn | GitHub | Portfolio

See you in my next blog article. Take care!!!

Ā