Three Steps to Develop a dApp

·

Developing a decentralized application (dApp) is an exciting entry point into the world of Web3, combining blockchain technology with real-world functionality. In this comprehensive guide, you'll learn how to build, deploy, and interact with a fully functional dApp that retrieves and stores the current ETH/USD price using smart contracts and Chainlink data feeds.

By the end of this tutorial, you’ll have a working end-to-end dApp built with Solidity, Hardhat, React, and ethers.js—ideal for beginners and developers looking to expand into decentralized development.


What Is a dApp?

A decentralized application, or dApp, operates on a blockchain network instead of relying on centralized servers. Unlike traditional Web2 apps, dApps run their backend logic through tamper-proof smart contracts, offering enhanced security, transparency, and resistance to censorship.

The core components of any dApp include:

👉 Discover how blockchain integration powers next-gen dApps.

Advantages of dApps

Challenges to Consider

Despite these hurdles, dApps represent the future of digital applications—transparent, secure, and user-owned.


Step 1: Create the Smart Contract

Our dApp will use a smart contract to fetch and store the current ETH/USD price from a Chainlink oracle. We'll write this in Solidity using Hardhat for development.

Set Up Your Development Environment

First, ensure you have:

Create your project directory:

mkdir chainlink-dapp-example
cd chainlink-dapp-example
mkdir backend
cd backend

Initialize Hardhat:

npm init -y
npm install --save-dev hardhat
npx hardhat

Select "Create a JavaScript project" and accept default settings.

Replace the default Lock.sol in the contracts folder with a new file named PriceConsumerV3.sol. Paste the following code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract PriceConsumerV3 {
    AggregatorV3Interface internal priceFeed;
    int public storedPrice;

    constructor() {
        priceFeed = AggregatorV3Interface(0x8A753747A1Fa494EC906cE90E9f37563A8AF630e); // Rinkeby ETH/USD feed
    }

    function getLatestPrice() public view returns (int) {
        (
            /*uint80 roundID*/,
            int price,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = priceFeed.latestRoundData();
        return price;
    }

    function storeLatestPrice() external {
        storedPrice = getLatestPrice();
    }
}

This contract uses Chainlink’s decentralized oracle network to pull live price data and stores it permanently on-chain.


Step 2: Deploy the Smart Contract

To deploy your contract to the Rinkeby testnet, you need:

Install required packages:

npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @chainlink/contracts dotenv --save

Update hardhat.config.js:

require("@nomicfoundation/hardhat-toolbox");
require('dotenv').config();

const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL || "";
const PRIVATE_KEY = process.env.PRIVATE_KEY || "";

module.exports = {
    defaultNetwork: "rinkeby",
    networks: {
        rinkeby: {
            url: RINKEBY_RPC_URL,
            accounts: [PRIVATE_KEY],
            saveDeployments: true,
        },
    },
    solidity: "0.8.9",
};

Create a .env file in /backend:

RINKEBY_RPC_URL=your_rpc_url_here
PRIVATE_KEY=your_private_key_here

Modify scripts/deploy.js:

const hre = require("hardhat");

async function main() {
    const PriceConsumer = await hre.ethers.getContractFactory("PriceConsumerV3");
    const priceConsumer = await PriceConsumer.deploy();
    await priceConsumer.deployed();
    console.log("Contract deployed to:", priceConsumer.address);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

Compile and deploy:

npx hardhat compile
npx hardhat run --network rinkeby scripts/deploy.js

Save the deployed contract address—you’ll need it for the frontend.

👉 Learn how secure smart contract deployment boosts dApp reliability.


Step 3: Build the Frontend with React and ethers.js

Now let’s create a user interface so users can interact with your deployed contract.

Initialize the React App

From the project root:

cd ..
npx create-react-app frontend
cd frontend
npm install bootstrap ethers

Clean up unnecessary files (logo.svg, App.test.js, etc.) and open /src/App.js.

Replace its content with:

import React, { useEffect, useState } from 'react';
import { ethers } from "ethers";
import 'bootstrap/dist/css/bootstrap.min.css';

function App() {
    const [storedPrice, setStoredPrice] = useState('');
    
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    
    const contractAddress = 'REPLACE_WITH_DEPLOYED_CONTRACT_ADDRESS';
    
    const ABI = [
        {
            "inputs": [],
            "stateMutability": "nonpayable",
            "type": "constructor"
        },
        {
            "inputs": [],
            "name": "getLatestPrice",
            "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }],
            "stateMutability": "view",
            "type": "function"
        },
        {
            "inputs": [],
            "name": "storeLatestPrice",
            "outputs": [],
            "stateMutability": "nonpayable",
            "type": "function"
        },
        {
            "inputs": [],
            "name": "storedPrice",
            "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }],
            "stateMutability": "view",
            "type": "function"
        }
    ];

    const contract = new ethers.Contract(contractAddress, ABI, signer);

    const getStoredPrice = async () => {
        try {
            const contractPrice = await contract.storedPrice();
            setStoredPrice(parseInt(contractPrice) / 100000000);
        } catch (error) {
            console.log("getStoredPrice Error: ", error);
        }
    };

    const updateNewPrice = async () => {
        try {
            const transaction = await contract.storeLatestPrice();
            await transaction.wait();
            await getStoredPrice();
        } catch (error) {
            console.log("updateNewPrice Error: ", error);
        }
    };

    useEffect(() => {
        getStoredPrice();
    }, []);

    return (
        <div className="container mt-5">
            <h1>ETH/USD Price dApp</h1>
            <div className="row mt-4">
                <div className="col-md-6">
                    <div className="card">
                        <div className="card-body">
                            <h5 className="card-title">Stored Price</h5>
                            <p className="card-text">ETH/USD: {storedPrice}</p>
                        </div>
                    </div>
                </div>
                <div className="col-md-6">
                    <div className="card">
                        <div className="card-body">
                            <h5 className="card-title">Update Price</h5>
                            <button className="btn btn-primary" onClick={updateNewPrice}>
                                Update
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default App;

Run the app:

npm run start

Connect your MetaMask wallet when prompted, click Update, confirm the transaction, and watch the price refresh automatically.


Frequently Asked Questions (FAQ)

What is a dApp in blockchain?

A dApp (decentralized application) is an application that runs on a blockchain network using smart contracts instead of centralized servers. It offers trustless, transparent, and censorship-resistant functionality.

How do I connect a frontend to a smart contract?

Use libraries like ethers.js or web3.js to interact with deployed contracts. The frontend connects via a Web3 provider (like MetaMask), using the contract address and ABI to call functions.

Can I update a smart contract after deployment?

Generally, no—smart contracts are immutable. However, developers use proxy patterns or upgradeable contracts to allow limited updates while maintaining security.

Why use Chainlink in a dApp?

Chainlink provides secure, decentralized oracle services that connect smart contracts to real-world data (like price feeds), enabling them to respond to external events reliably.

Is MetaMask necessary for dApp interaction?

Yes—for most Ethereum-based dApps, MetaMask acts as the Web3 wallet interface, allowing users to sign transactions and manage identities securely.

Where can I host a dApp frontend?

You can host the frontend on traditional cloud platforms (e.g., AWS, Vercel) or use decentralized storage like IPFS for full decentralization.


Final Thoughts

Building a dApp involves three core steps: writing a smart contract, deploying it to a blockchain, and connecting it to a user-friendly frontend. This tutorial walked you through creating a functional ETH price tracker using Solidity, Hardhat, React, and Chainlink oracles.

As Web3 adoption grows, mastering dApp development opens doors to innovation in DeFi, NFTs, DAOs, and more.

👉 Start building your own dApp today with secure tools and resources.