Developing decentralized applications (Dapps) on Ethereum combines smart contracts with user-friendly front-ends, creating trustless, transparent systems. In this comprehensive guide, you’ll learn how to build, test, and deploy a full Dapp using Truffle, Solidity, and Web3.js — all through a practical pet adoption example.
Whether you're new to blockchain development or expanding your skills, this tutorial walks you through every step: from setting up your environment to interacting with smart contracts via MetaMask in the browser.
Why This Dapp Matters
Pete runs a pet shop with 16 pets and wants to enable transparent pet adoptions using blockchain. By building this Dapp, users can adopt pets securely, with each transaction recorded immutably on the Ethereum network. This project demonstrates core blockchain concepts like contract deployment, state management, and front-end integration.
Core Keywords for SEO
- Decentralized application (Dapp)
- Truffle framework
- Web3.js
- Smart contract development
- Ethereum blockchain
- Solidity programming
- Contract deployment
- MetaMask integration
These keywords naturally appear throughout the content to enhance search visibility while maintaining readability.
Setting Up Your Development Environment
Before writing any code, ensure your development environment is ready.
Prerequisites
You should have:
- Basic knowledge of JavaScript and HTML
- Familiarity with Ethereum and smart contracts
If you're unfamiliar with Ethereum fundamentals, consider reviewing introductory resources before continuing.
Install Required Tools
- Node.js – Download and install from nodejs.org
Truffle – Install globally via npm:
npm install -g truffle- Ganache – Download the desktop app or use CLI for local blockchain simulation
Ganache replaces the older testrpc tool, providing a personal Ethereum blockchain for testing.👉 Get started with blockchain development tools today.
Creating the Pet Shop Project
Start by scaffolding a new Truffle project using the official Pet Shop box.
mkdir pet-shop-tutorial
cd pet-shop-tutorial
truffle unbox pet-shopThis command downloads a preconfigured frontend and sets up the basic structure:
pet-shop-tutorial/
├── contracts/ # Solidity smart contracts
├── migrations/ # Deployment scripts
├── test/ # Test cases
├── truffle-config.js # Configuration file
└── src/ # Frontend files (HTML, JS, CSS)You can also initialize a blank project with truffle init, but using unbox saves time with ready-made UI components.
Writing the Smart Contract in Solidity
Smart contracts serve as the backend logic of your Dapp. We'll write one in Solidity, compatible with version 0.5.0+.
Create contracts/Adoption.sol:
pragma solidity ^0.5.0;
contract Adoption {
address[16] public adopters;
// Adopt a pet by ID
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15, "Pet ID out of range");
adopters[petId] = msg.sender;
return petId;
}
// Retrieve list of adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
}This contract allows users to adopt a pet (by index 0–15) and stores their Ethereum address. The getAdopters function enables UI retrieval without gas costs.
Compiling and Deploying the Contract
Compile the Contract
In your terminal:
truffle compileOutput:
Compiling ./contracts/Adoption.sol...
Writing artifacts to ./build/contractsTruffle compiles Solidity into EVM bytecode and generates JSON artifacts containing ABI and metadata.
Prepare Migration Script
Create migrations/2_deploy_contracts.js:
var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) {
deployer.deploy(Adoption);
};Migrations ensure contracts are deployed in order and track changes across networks.
Run Ganache and Deploy
Launch Ganache to start a local blockchain on port 7545.
Then run:
truffle migrateSuccessful output includes:
Deploying Adoption...
Adoption: 0x345ca3e014aaf5dca488057592ee47305d9b3e10Your contract is now live on the local chain.
Testing the Smart Contract
Reliable Dapps require robust testing. Let’s write tests in Solidity.
Create test/TestAdoption.sol:
pragma solidity ^0.5.0;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
Adoption adoption = Adoption(DeployedAddresses.Adoption());
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}
function testGetAdopterAddressByPetId() public {
address expected = address(this);
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
}
function testGetAdoptersArray() public {
address expected = address(this);
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}
}Run tests:
truffle testExpected result:
3 passing (554ms)All tests pass — your logic is sound.
Connecting Frontend with Web3.js
Now connect the UI to interact with the deployed contract.
Initialize Web3
Update src/js/app.js → initWeb3():
initWeb3: async function() {
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
await window.ethereum.enable();
} catch (error) {
console.error("User denied account access");
}
} else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
} else {
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
}Modern browsers inject window.ethereum. This ensures compatibility with MetaMask.
👉 Start interacting with Ethereum-based Dapps securely.
Load and Use the Contract
Update initContract():
initContract: function() {
$.getJSON('Adoption.json', function(data) {
var AdoptionArtifact = data;
App.contracts.Adoption = TruffleContract(AdoptionArtifact);
App.contracts.Adoption.setProvider(App.web3Provider);
return App.markAdopted();
});
return App.bindEvents();
}Truffle generates Adoption.json during compilation — it contains ABI and network info.
Handle Adoption in UI
Update markAdopted() to reflect adoption status:
markAdopted: function() {
var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
return adoptionInstance.getAdopters.call();
}).then(function(adopters) {
for (let i = 0; i < adopters.length; i++) {
if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
}
}
}).catch(function(err) {
console.log(err.message);
});
}Update handleAdopt() to send transactions:
handleAdopt: function(event) {
event.preventDefault();
var petId = parseInt($(event.target).data('id'));
var adoptionInstance;
web3.eth.getAccounts(function(error, accounts) {
if (error) { console.log(error); }
var account = accounts[0];
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
return adoptionInstance.adopt(petId, { from: account });
}).then(function(result) {
return App.markAdopted();
}).catch(function(err) {
console.log(err.message);
});
});
}Running the Dapp in Browser
Set Up MetaMask
- Install MetaMask (browser extension)
Import wallet using Ganache’s default mnemonic:
candy maple cake sugar pudding cream honey rich smooth crumble sweet treat- Connect to custom RPC:
http://127.0.0.1:7545
Your wallet now connects to your local blockchain.
Launch the Development Server
The pet-shop box includes lite-server for easy hosting.
Start it with:
npm run devYour browser opens http://localhost:3000, showing the pet shop interface.
Click Adopt on any pet → MetaMask prompts for confirmation → Submit → Success!
Each adoption updates the button state and records the transaction on-chain.
👉 Explore more ways to interact with decentralized networks.
Frequently Asked Questions (FAQ)
Q: What is Truffle used for in Dapp development?
A: Truffle is a development framework that simplifies compiling, testing, and deploying smart contracts. It provides built-in tools like scriptable migration support, network management, and automated contract testing.
Q: Why do I need Ganache?
A: Ganache creates a local Ethereum blockchain for testing. It gives you control over accounts, balances, and mining — perfect for debugging before deploying to live networks.
Q: How does Web3.js interact with smart contracts?
A: Web3.js connects your frontend to an Ethereum node (like MetaMask). Using the contract’s ABI, it lets you call functions — call() for reading data (free), and send() for writing data (gas required).
Q: Can I deploy this Dapp on the mainnet?
A: Yes! After testing locally, configure Truffle to connect to networks like Goerli or Ethereum Mainnet using HDWalletProvider or WalletConnect, then run truffle migrate --network mainnet.
Q: Is Solidity hard to learn?
A: If you know JavaScript or C-style languages, Solidity will feel familiar. Its syntax is straightforward, though security best practices (like input validation) are critical due to irreversible transactions.
Q: What happens if two people try to adopt the same pet?
A: Ethereum processes transactions sequentially. The first valid transaction succeeds; the second fails because the state has already changed — ensuring no double adoptions occur.
With this foundation, you're ready to explore advanced topics like IPFS for decentralized storage or Chainlink for oracles. Keep building — the decentralized future needs developers like you.