We've got the contract setup and use Chainlink Functions to pull in the latest news article. We can see the titles, links, and dates published in Remix. That's awesome but not super user-friendly. Let's add a simplistic front end to present this data.
🚀 Initiate Liftoff
All of the code for this project can be found on GitHub
We'll be using Astro to build out the frontend. Let's create a skeleton project.
npm create astro@latest
Head to cd ./distributed-news and run npm run dev. You should now be able to access Astro at http://localhost:4321, if you visit that site, Astro should greet you. The basics are in place.
Read From The Blockchain
To read our data from the smart contract we deployed, we'll need to create 2 files. To do that, we must install a few NPM packages first. If your server is running, stop it with Ctrl-C, and run
npm install ethers dotenv node-fetch jsdom
to add them to our project. Now, we can move to creating our files.
First, we'll get the ABI (Application Binary Interface) from Remix. This will allow ethers.js to know how to interact with our contract. To find the ABI, head back to Remix.
Under the compiler tab, look for the copy ABI button at the bottom.
Once you've copied the code, head to your code editor, create a new file, distributed-news/src/lib/newsArchiveABI.json, and paste the ABI.
Next, we'll need to create the script to pull the data off the blockchain. Create a new file, distributed-news/src/lib/newsArchive.js and add the following code.
// Import necessary modules from ethers libraryimport { ethers, JsonRpcProvider } from"ethers";// Import the dotenv module to load environment variablesimport { config } from"dotenv";// Import the ABI of the NewsArchive contractimport NewsArchiveABI from"./newsArchiveABI.json";// Load environment variables from .env fileconfig();// Get provider URL and contract address from environment variablesconstproviderUrl=process.env.PROVIDER_URL;constcontractAddress=process.env.CONTRACT_ADDRESS;// Create a new JSON-RPC providerconstprovider=newJsonRpcProvider(providerUrl);// Create a new contract instance with the NewsArchive ABIconstnewsArchiveContract=newethers.Contract( contractAddress, NewsArchiveABI, provider);// Define an async function to get all articles from the contractasyncfunctiongetAllArticles() {try {// Call the getAllArticles function of the contractconstarticles=awaitnewsArchiveContract.getAllArticles();// Return the articlesreturn articles; } catch (error) {// Log the error and return an empty arrayconsole.error("Error fetching articles:", error);return []; }}// Export the getAllArticles function as the default exportexportdefault getAllArticles;
Environment Variables
You may have noticed that we are using dotenv to load values for PROVIDER_URL and CONTRACT_ADDRESS, you'll need to create distributed-news/.env to house those values. Be sure to replace the value for CONTRACT_ADDRESS with the contract address you deployed.
# RPC for sepoliaPROVIDER_URL="https://rpc.sepolia.org"CONTRACT_ADDRESS="YOUR_CONTRACT_ADDRESS"
Displaying The Results
One last step! We are almost there. We need to update distributed-news/src/pages/index.astro to display the articles we've saved.
First, let's update the frontmatter.
---import fetch from"node-fetch";import { JSDOM } from"jsdom";// Import the getAllArticles function from the newsArchive.js fileimport getAllArticles from"../lib/newsArchive.js";// Initialize articles variable and error flaglet articles;let errorOccurred =false;try {// Try to fetch all articles articles =awaitgetAllArticles();} catch (error) {// If an error occurs, log it and set the error flagconsole.error("Failed to get articles:", error); articles = []; errorOccurred =true;}asyncfunctionfetchOGData(url) {try {constresponse=awaitfetch(url);consthtml=awaitresponse.text();constdom=newJSDOM(html);constdoc=dom.window.document;let ogTitle =doc.querySelector('meta[property="og:title"]')?.content || url;constogImage=doc.querySelector('meta[property="og:image"]')?.content;return { ogTitle, ogImage, url }; } catch (error) {console.error("Error fetching OG data:", error);return { ogTitle:"No title", ogImage:"No image", url }; }}asyncfunctionarticlesWithOGData(articles) {returnawaitPromise.all(articles.map(fetchOGData));}let allArticles =awaitarticlesWithOGData(articles);---
Once that's updated we can use the variable articles to display them in the browser.
We'll setup a conditional rendering block to check for any errors and if there are none, display the articles.
<html> <body> <h1>News Articles</h1> {// If an error occurred, display an error message// Otherwise, display the list of articles errorOccurred ? ( <p>Sorry, we're unable to fetch articles at the moment.</p> ) : ( <ul> {allArticles.map((article) => ( <li> <article> <ahref={article.url}> <h2>{article?.ogTitle}</h2> </a> <imgsrc={article?.ogImage} alt="" /> </article> </li> ))} </ul> ) } </body></html>
What it lacks in style, it makes up for in functionality.
Add Some Style
You might be cringing at the lack of style, lets fix that and make things look just a little bit nicer.