Blockchain technology and Ethereum's innovation with Turing-complete smart contracts provide the ability to develop serverless applications with backend code running on a decentralized network.
This removes reliance on centralized servers and builds censorship resistance into the core of our apps.
Learn why we feel Web3 is a sham. Find out in this guide: Web3 Vs Web2: The Untold Lies of Blockchain Technology.
This comprehensive guide will demonstrate these strengths by architecting the backend smart contract for a blockchain-powered Todo List DApp.
Our tech stack includes:
Solidity - For implementing self-executing logic on Ethereum
Hardhat - Local Ethereum development and testing framework
React - For building a sleek frontend UI
By the end, you will gain practical skills in crafting complex smart contracts for real-world use.
Development Environment Setup
The Hardhat framework sets up an integrated local environment for rapidly building Ethereum-based decentralized applications. Hardhat includes utilities for compiling, deploying, testing, and debugging Solidity smart contracts - great for boosting developer productivity.
First, install Hardhat:
npm install --save-dev hardhat
Then initialize a sample project with:
npx hardhat
Choose the Create a sample project
option when prompted to generate a starter kit, including a configurable hardhat.config.js
file and folders for contracts and tests.
Boost Your Blockchain Development Game with VS Code The Ultimate Setup Guide
We need additional plugins for enhanced capabilities around Ethers wallet integration and advanced contract testing using the Waffle library:
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
Ethers.js assists with wallet management used later for deploying contracts, while Waffle handles spinning up a testing blockchain network that mimics the Ethereum mainnet.
Finally, connect your Alchemy or Infura API key in hardhat.config.js
to deploy to public test networks:
module.exports = {
solidity: "0.8.4",
networks: {
rinkeby: {
url: `${process.env.ALCHEMY_API_URL}`,
accounts: [`0x${process.env.ACCOUNT_PRIVATE_KEY}`]
},
}
}
Smart Contract Design
With our Hardhat-powered development environment set up, let's start coding the smart contract backend!
Inside contracts/TodoList.sol
, first structure a Todo data model containing essential fields via a Solidity struct:
struct Todo {
uint id;
string text;
bool completed;
}
Two critical aspects of any functional Todo list include:
Persisting added Todo items
Tracking completion status |
Hence, our data model stores a unique id
, a text
description of the actual todo task, and a boolean flag completed
denoting whether it's been marked done.
We manage storage via a list declared as:
Todo[] public todos;
With the data foundation in place, we specify key application logic around managing todos in the form of Solidity functions:
createTodo - Insert new Todo into list by pushing to todos
array
toggleCompleted - Flip the completed
flag to true/false
getTodos - Fetch and return the todos
array
Modular design best practices recommend emitting events in response to state changes. This allows detached frontends and analytics engines to conveniently listen for logs instead of directly observing contract storage, which consumes higher gas fees.
event TodoCreated(uint indexed id, string text);
function createTodo(string calldata _text) external {
todos.push(Todo(_nextTodoId++, _text, false));
emit TodoCreated(_nextTodoId, _text);
}
Here, when a new todo gets created, we emit a TodoCreated
an event containing the auto-incremented id
and text
details.
Thoroughly Testing the Contract
With core functionality coded, it is prudent software engineering practice to comprehensively test smart contracts before relying on their correctness.
Waffle provides a testing environment by running a temporary Ethereum node optimized for rapid iteration. Under test/TodoList.spec.js
we leverage Waffle's integration with Mocha/Chai for familiar syntax:
describe('TodoList', () => {
let todoList
let owner
beforeEach(async () => {
// Deploy a new contract instance before each test
owner = new ethers.Wallets.createRandom()
const TodoList = await ethers.getContractFactory('TodoList')
todoList = await TodoList.deploy()
})
it('creates new todo', async () => {
await todoList.connect(owner).createTodo('Get milk')
const todos = await todoList.getTodos()
assert.equal(todos.length, 1)
})
})
We test critical functionality like creating todos and retrieving them for validation against expected behavior per specifications. These end-to-end integration style tests capture real usage scenarios.
Combined with unit tests and code review, our comprehensive test suite guarantees the TodoList contract works correctly before launch.
Deploying to Public Testnet
Once satisfied with functionality on the locally emulated blockchain, we script a pipeline for standing up a live version on the public Rinkeby test network.
Configure Alchemy/Infura endpoints under Hardhat to connect to Rinkeby. Then, simply import helpers and deploy in scripts/deploy.js
:
async function main() {
const TodoList = await ethers.getContractFactory('TodoList')
const todoList = await TodoList.deploy()
await todoList.deployed()
console.log('TodoList deployed to: ', todoList.address)
}
main()
Run via npx hardhat run scripts/deploy.js --network rinkeby
.
The console prints the deployed contract address on success. Share this address with the DApp frontend to enable Web3 connectivity.
With an active instance now running on public infrastructure outside our proprietary control, the Todo list inherits blockchain's censorship resistance, and persistent availability guarantees through decentralization.
Conclusion
In this guide, we built a full-fledged backend for a decentralized Todo list DApp showcasing concepts like:
Spinning up a Hardhat-powered dev environment
Structuring data models with Solidity
Developing core create, read, and update contract functions
Writing comprehensive integration test suites with Waffle
Scripting deployment pipelines for publishing on Ethereum's public testnets
Composing the backend lets us realize the Web3 vision of crafting resilient software immune from centralized failure points.
Equipped with these learnings, integrating any frontend such as React via web3 libraries like Ethers.js can now connect users to this serverless data layer living permanently on the blockchain.
This is the first section, in the next section, we are going to dive into deployment and testing our contract.
If you like our work and want to help us continue dropping content like this, buy us a cup of coffee.
If you find this post exciting, find more exciting posts on Learnhub Blog; we write everything tech from Cloud computing to Frontend Dev, Cybersecurity, AI, and Blockchain.