To build a dApp on Ethereum, you combine a Solidity smart contract (your backend) with a JavaScript frontend that interacts with it via a library like ethers.js. This practical guide walks you through creating a simple, functional dApp from scratch, covering environment setup, smart contract development, local testing, frontend connection, and testnet deployment.
What You'll Build: A Simple Ethereum dApp
This dApp will consist of:
- A Solidity smart contract with functions to set and get a string message.
- A React frontend that connects to the contract using ethers.js, allowing users to read the current message and submit a new one.
- A local development workflow using Hardhat for compiling, testing, and deploying to a personal blockchain.
- Instructions to deploy the same contract to the Sepolia testnet, making it accessible via a public RPC URL.
By the end, you'll have a working template you can extend with more complex logic, token interactions, or additional state variables.
Prerequisites for Building on Ethereum
Before you start, ensure you have the following:
- Basic JavaScript knowledge, You'll be writing React components and using Node.js tools.
- Node.js (v18 or later) and npm installed on your machine. Check with
node -vandnpm -v. - MetaMask browser extension (for testnet deployment and frontend interaction).
- A code editor, VS Code with the Solidity extension is recommended.
- Familiarity with the command line, You'll run npm scripts and Hardhat commands.
No prior blockchain experience is required, but understanding the concept of a transaction and a wallet address will help.
Set Up Your Ethereum Development Environment
We'll use Hardhat, a modern development framework that simplifies compiling, testing, and deploying smart contracts. Follow these steps:
- Create a new project directory and initialize it:
mkdir ethereum-dapp
cd ethereum-dapp
npm init -y - Install Hardhat and ethers.js (the library for interacting with Ethereum):
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox - Initialize a Hardhat project:
npx hardhat initThis creates a
contracts,scripts, andtestfolder. - Install the frontend dependencies later when we build the UI. For now, your backend environment is ready.
Write Your First Smart Contract with Solidity
Navigate to the contracts folder and create a new file named MessageBoard.sol. Open it and write the following code:
Key Solidity concepts in this contract:
pragma solidity, Specifies the compiler version. Always use the latest stable release.contract MessageBoard, The main structure, similar to a class in JavaScript.string private message, A state variable stored on the blockchain.publicfunctions, Can be called externally by users or other contracts.view, Indicates the function reads state but does not modify it (no gas cost when called via a node).event, Used to emit logs that frontends can listen to.
Compile and Test Your Smart Contract
Testing is critical because smart contracts are immutable once deployed. A bug can't be patched easily. Let's compile and write automated tests.
- Compile the contract:
npx hardhat compileThis generates the ABI (Application Binary Interface) and bytecode in the
artifactsfolder. - Write a test file in the
testfolder. Createmessage-board.js:
const { expect } = require("chai");describe("MessageBoard", function () {
it("Should set and get the message", async function () {
const MessageBoard = await ethers.getContractFactory("MessageBoard");
const messageBoard = await MessageBoard.deploy();
await messageBoard.deployed();await messageBoard.setMessage("Hello, Ethereum!");
expect(await messageBoard.getMessage()).to.equal("Hello, Ethereum!");
});it("Should emit an event when the message changes", async function () {
const MessageBoard = await ethers.getContractFactory("MessageBoard");
const messageBoard = await MessageBoard.deploy();
await messageBoard.deployed();await expect(messageBoard.setMessage("New message"))
.to.emit(messageBoard, "MessageUpdated")
.withArgs("New message");
});
}); - Run the tests:
npx hardhat testYou should see both tests passing. This validates the contract logic before deployment.
Deploy Your dApp to a Local Blockchain
Hardhat includes a built-in local Ethereum network for fast, risk-free development. To deploy your contract locally:
- Start the local node (it will run in the terminal):
npx hardhat nodeKeep this terminal running.
- Open a new terminal and deploy the contract to this local network. Create a script in
scripts/deploy.js:
async function main() {
const MessageBoard = await ethers.getContractFactory("MessageBoard");
const messageBoard = await MessageBoard.deploy();
await messageBoard.deployed();
console.log("MessageBoard deployed to:", messageBoard.address);
}main().catch((error) => {
console.error(error);
process.exitCode = 1;
}); - Run the deployment script against the local node:
npx hardhat run scripts/deploy.js --network localhostNote the contract address printed in the console, you'll need it for the frontend.
Connect a Frontend to Your Smart Contract
Now we'll build a React interface that reads and writes to your deployed contract using ethers.js.
- Create a React app in a separate folder (or use Vite for speed):
npx create-react-app frontend
cd frontend - Install ethers.js:
npm install ethers - Replace the contents of
src/App.jswith:
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import MessageBoardABI from './MessageBoardABI.json'; // copy from artifactsconst contractAddress = "0x..."; // Replace with your deployed address
function App() {
const [message, setMessage] = useState('');
const [input, setInput] = useState('');
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);useEffect(() => {
const init = async () => {
if (window.ethereum) {
const provider = new ethers.providers.Web3Provider(window.ethereum);
setProvider(provider);
const signer = provider.getSigner();
setSigner(signer);
const contract = new ethers.Contract(contractAddress, MessageBoardABI, provider);
const currentMessage = await contract.getMessage();
setMessage(currentMessage);
}
};
init();
}, []);const setNewMessage = async () => {
if (!signer) return;
const contract = new ethers.Contract(contractAddress, MessageBoardABI, signer);
const tx = await contract.setMessage(input);
await tx.wait();
setMessage(input);
setInput('');
};return (
<div style={{ padding: '2rem' }}>
<h1>Ethereum Message Board</h1>
<p>Current message: <strong>{message}</strong></p>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter new message"
/>
<button onClick={setNewMessage}>Update Message</button>
</div>
);
}export default App;
- Copy the ABI from
artifacts/contracts/MessageBoard.sol/MessageBoard.jsoninto a local filesrc/MessageBoardABI.json(just theabiarray). - Replace
contractAddresswith the address from your local deployment. - Run the frontend:
npm startOpen
http://localhost:3000. Make sure MetaMask is connected tolocalhost:8545(Hardhat's default). You should see the initial empty message, and you can update it.
Deploy Your dApp to an Ethereum Testnet
To make your dApp accessible on a public network, deploy to the Sepolia testnet. You'll need test ETH and a provider service like Infura.
- Get Sepolia test ETH, Use a faucet (e.g.,
sepoliafaucet.com) and your MetaMask wallet address. - Create an Infura project, Go to
infura.io, create a free account, and get an API key for the Sepolia network. - Configure Hardhat for Sepolia, Edit
hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");module.exports = {
solidity: "0.8.19",
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/YOUR_INFURA_KEY`,
accounts: [process.env.PRIVATE_KEY] // Use environment variable for security
}
}
};Install dotenv:
npm install dotenvand create a.envfile with your private key (from MetaMask, without the 0x prefix). - Deploy to Sepolia:
npx hardhat run scripts/deploy.js --network sepoliaThis will take a few seconds. Note the new contract address.
- Update your frontend, Change
contractAddressto the Sepolia address. Also, update the provider to use a public RPC URL (e.g.,https://sepolia.infura.io/v3/YOUR_INFURA_KEY) for read operations, but keep MetaMask for writes. - Deploy the frontend, You can host it on Vercel, Netlify, or any static hosting service. Build with
npm run buildand upload thebuildfolder.
Your dApp is now live on a public testnet! Users can connect their MetaMask, switch to Sepolia, and interact with your contract.
Next Steps and Best Practices
- Always test thoroughly, Use Hardhat's testing framework to cover edge cases.
- Keep contract size under 24KB, The EVM has a contract size limit. Use external libraries or the Diamond Pattern if needed.
- Use the latest Solidity version, Security fixes are regularly released.
- Secure your frontend, Never hardcode private keys. Use MetaMask for transaction signing.
Building a dApp on Ethereum is a rewarding skill. This tutorial gives you a solid foundation to create decentralized applications that can be deployed to the Ethereum Virtual Machine (EVM) and accessed globally.

















