A Practical Introduction to web3.js:

Welcome to another installment of the Learn Solidity series. In the last article, we saw how to implement the factory pattern. If you’ve been following from the beginning of this series, then you’ve got the basics down and can start writing your own smart contracts.
Therefore, today I would like to give you the big picture of building decentralized applications and introduce you to web3.js — the missing piece to start building your own dapps.
Before diving into the details of what web3.js is and how it works, I would like to set up the context and start by answering a simple question: Why web3.js?
A decentralized application has three main components:
- The front end: Takes input from the user and builds the requests to be sent to the smart contracts.
- The wallet: Signs transactions and sends them to the network.
- Smart contracts: Here is where you write the business logic of the dapp.
The question now is how to interact with smart contracts from the front end using only JavaScript.
Before web3.js came along, the only way we could interact with smart contracts was through the json-rpc API provided by the Ethereum node that acted as a json-rpc server. That was not an easy task.
But thanks to web3.js, you don’t have to worry about the low-level details of JSON-RPC calls anymore since it provides an abstraction of the Ethereum json-rpc interface. As such, you can start interacting with an Ethereum node using plain JavaScript. Simply put, web3.js exposes JSON-RPC APIs as JavaScript APIs.
How web3.js Works
As we said before, in order to interact with the Ethereum network, we need to send json-rpc calls to an Ethereum node, which is what web3.js does under the hood. So how does it do it?
In order to translate JavaScript code to json-rpc requests, web3.js uses what we call a provider, which is a JavaScript object compliant with EIP 1193 specification, and implements the request
method responsible for making an Ethereum RPC method call. Web3.js has its own implementation of the specification mentioned above and made it available under web3.providers
, where you can access the three following providers as mentioned in the documentation: HttpProvider
, WebsocketProvider
, and IpcProvider
.
More information about these providers can be found in the documentation.
Other projects have also implemented this specification, such as MetaMask, which injects the provider object in the browser under window.ethereum
. Older versions of Metamask used to inject an instance of web3 under window.web3
.
Once we have a provider, we can get an instance of web3 using the new
keyword:
let web3 = new Web3(Web3.givenProvider || 'ws://some.local-or-remote.node:8546');
What you should keep in mind here is that web3.js needs a provider object configured with the information of the wallet that’s going to sign the transaction and send it to the network. This provider is either going to be injected in the browser if you’re using web3 in the front end or an object that you’re going to build yourself using websocketprovider
or IpcProvider
.
Note: MetaMask uses Infura as a node provider. That’s why you won’t have to install an Ethereum client on your computer to be able to interact with the Ethereum network.
A Quick Look at the API
web3.js does not only allow communication with Ethereum nodes but also with Whisper and Swarm nodes. It comes with five main packages :
web3.eth
: Allows interaction with an Ethereum blockchain and Ethereum smart contracts.web3.bzz
: Allows interaction with Swarm, the decentralized file store.web3.shh
: Allows interaction with the Whisper protocol for broadcasting.web3.utils
: Provides utility functions for Ethereum dapps, like converting strings to hex literals and Ether values to Wei.web3.*.net
: Allows interaction with an Ethereum node’s network properties, such as the network ID or the peer count.
Build Your First Dapp With web3.js
Now that we have the theory out of the way, let’s get our hands dirty and build our first dapp. In this example, we are going to build a greeting dapp that stores a default greeting string and allow the user to update it. For the wallet, we will use MetaMask. You can add the extension by clicking on the download link on the home page of their website.
1. Build the contract and deploy it to the network
Start by creating an empty project in your workspace called greeting
and initialize it using truffle init
:
> mkdir greeting
> cd greeting
> truffle init
I’m assuming that you’ve already installed Truffle. If you haven’t, you can get it using the following command:
npm install -g truffle
Open the project using your favorite code editor (I’m using VS Code). Next, edit the truffle-config.js
file and update the networks with the IP and port of your Ethereum network (you can use Ganache for that). You also need to configure the compiler version to use the version that you’ve installed.
If you don’t have a Solidity compiler yet, you can install it using the following command:
> npm install -g solc// check the installed version
> solc --version
Now, it’s time to create the greeting
contract in your project directory. Type the following command:
> truffle create contract Greeting
Copy-paste the following code:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.8.0;
contract Greeting {
string public greeting = "hello";
function sayHello() external view returns (string memory) {
return greeting;
}
function updateGreeting(string calldata _greeting) external {
greeting = _greeting;
}
}
We’re almost set up. We just need to add a 2_deploy_greeting.js
migration file under the migrations
folder:
const Greeting = artifacts.require("Greeting");module.exports = function (deployer) {
deployer.deploy(Greeting);
};
You can now compile and deploy your smart contract to the network using:
> truffle compile
> truffle migrate
2. Connect the front end with the smart contract
- Set up the front end
Create a new folder in the project called client
with package.json
and index.html
files:
> mkdir client
> cd client
> npm init -y
> touch index.html
Install web3.js and lite-server dependencies:
> npm i web3 --save
> npm i lite-server --save-dev //for running a development server
Create a new folder called src
and add two scripts: index.js
and utils.js
. You also need to add the two scripts in the HTML file using the script
tag along with the web3 script:
<script type="text/javascript" src="node_modules/web3/dist/web3.min.js"></script><script type="text/javascript" src="src/utils.js"></script><script type="text/javascript" src="src/index.js"></script>
- Get a web3 instance
Once you have web3.js as a dependency in your project, all you need to do is to instantiate a web3 object using an instance of a provider in order to enjoy all that web3 has to offer.
We will use the window.ethereum
MetaMask provider injected in the browser and ask for the user’s permission to access their accounts using window.ethereum.request
, as described in the MetaMask docs.
Open the utils.js
file and add this function:
const getWeb3 = () => {
return new Promise((resolve, reject) => {
window.addEventListener("load", async () => {
if (window.ethereum) {
const web3 = new Web3(window.ethereum);
try {
// ask user permission to access his accounts
await window.ethereum.request({ method: "eth_requestAccounts" });
resolve(web3);
} catch (error) {
reject(error);
}
} else {
reject("Must install MetaMask");
}
});
});
};
- Create a contract instance
As described in the docs, in order to create a contract instance, we need the contract ABI and its address. If you take a look at the artifacts in the build directory, you will find a file named Greeting.json
. If you open it, you will find a lot of information about the contract, including the contract name, the ABI, etc. If you scroll to the end of the file, you will find a network field containing the ID of the networks where the contract is deployed and the corresponding address that we will use to instantiate the contract instance with web3.
Create a new folder called contracts
under the client
folder and copy-paste the Greeting.json
file. You will need also to install jQuery to be able to read the contents of the JSON file:
> npm i jquery
First, we need to get the ID of the network to which MetaMask is connected using web3.eth.net.getId()
.
Note: I’m assuming that you have already configured MetaMask to use Ganache. If you’re not sure how to do it, you can follow this guide.
Next, we will use the returned ID to get the address of the contract from the Greeting.json
file, which will also provide us with the contract ABI and create an instance of the contract using web3.eth.Contract
:
const getContract = async (web3) => {
const data = await $.getJSON("./contracts/Greeting.json");
const netId = await web3.eth.net.getId();
const deployedNetwork = data.networks[netId];
const greeting = new web3.eth.Contract(
data.abi,
deployedNetwork && deployedNetwork.address
);
return greeting;
};
3. Interact with the smart contract
Once we have created an instance of the contract, we can start calling its methods using myContract.methods.myMethod([arguments])
, as described in the docs.
If the function to be called is pure or read-only, you need to use:
myContract.methods.myMethod([arguments]).call()
If the function to be called is going to modify the state (aka transaction), you will need to use:
myContract.methods.myMethod([arguments]).send()
More details can be found in the docs.
const displayGreeting = async (greeting, contract) => {
greeting = await contract.methods.sayHello().call();
$("h2").html(greeting);
};
const updateGreeting = (greeting, contract, accounts) => {
let input;
$("#input").on("change", (e) => {
input = e.target.value;
});
$("#form").on("submit", async (e) => {
e.preventDefault();
await contract.methods
.updateGreeting(input)
.send({ from: accounts[0], gas: 40000 });
displayGreeting(greeting, contract);
});
};
async function greetingApp() {
const web3 = await getWeb3();
const accounts = await web3.eth.getAccounts();
const contract = await getContract(web3);
let greeting;
displayGreeting(greeting, contract);
updateGreeting(greeting, contract, accounts);
}
greetingApp();
You can find the full code on GitHub.
Conclusion
I hope that you enjoyed this tutorial. If you did, then stick around because there’s a lot more to come.
For more blogs, click here
Leave a comment