Back to homepage

Step-by-step web3 DApp built with Solidity and Vue.js (part 1)

Published

You can find the second part of this series here, and the third one here

Project overview

In this article we're going to build a basic decentralized web app that will store messages from users.

web3 message board app

The application is live and you can play with it here. Just make sure you're using Metamask with the Rinkeby network 😉

Web 2.0 / 3.0 differences

The main difference between how I'd build this app as a common web application (or web 2.0) vs a decentralized app (web 3.0) are going to be the authentication, data storage and business logic layer:

  • Instead of building an authentication system, with user and password or an OAuth provider (like logging in with Google or Auth0), I'll use Metamask and use the user's wallet address to identify every user.

  • Instead of storing the messages from the users in a normal database like MongoDB or PostgreSQL, the app will store them in the blockchain

  • Typical web 2.0 applications use APIs as a bridge between the front end and the database. In this web 3.0 project I'll replace the API for a smart contract written in Solidity.

There is one similarity between a web 2.0 and 3.0, and that's the front end. In both cases, a web is, well... a website so any front end framework like React or Vue.js can be used.

Features

As mentioned, this is going to be a very basic project but it'll be enough to cover a lot of concepts needed to understand how to build decentralized apps. The main features will be:

  • Users can access the app after connecting their wallet.
  • Users can view messages posted by other users
  • Users can post a message, which will require a small fee.

Tech stack

Requirements

  • Node.js & NPM
  • Vue CLI

Front end:

  • Vue.js v3 + TypeScript
  • TailwindCSS (styling, optional)
  • Metamask

Smart Contract:

  • Solidity (Hardhat)
  • Rinkeby (Ethereum TestNet)

Creating the smart contract with Hardhat

I'll use my web3 template project as a starting point

Create a folder for your project and navigate to it with your terminal. Then run npx hardhat and choose the option "Create sample project". Also select the option to install any required dependencies.

By default, Hardhat will generate some files in the /contracts and /scripts folders. I prefer to have the smart contracts source files in /solidity/contracts and the compiled files in /src/artifacts so for that, I created the folders manually and changed the hardhat.config.js file as follows:

// .... some tasks above this

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: '0.8.4',

  paths: {
    sources: './solidity/contracts',
    artifacts: './src/artifacts',
  },
};

Check out my article about first steps with Hardhat

Once the configuration is completed, I created the file /solidity/contracts/MessagePortal.sol, which is the smart contract that will be used to store and return the user's messages to the front end.

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.4;

import "hardhat/console.sol";

contract MessagePortal {
    // number of total messages
    uint256 totalMessages;

    // struct definition of a message
    struct Message {
        string text;
        address from;
        uint256 datetime;
    }
    // stores all messages
    Message[] allMessages;

    // counter of messages per user
    mapping(address => uint256) messagesPerUser;

    // event that it's sent to the front end
    event NewMessage(address indexed from, uint256 timestamp, string message);

    constructor() {
        console.log("Yayyy");

    }
    // saves message in the blockchain and updates counters
    function sendMessage(string memory _newMessage) public {
        console.log("%s has sent a message!", msg.sender);

        // saves message in array
        allMessages.push(Message(_newMessage, msg.sender, block.timestamp));

        // update counters
        totalMessages += 1;
        messagesPerUser[msg.sender] += 1;

        console.log(
            "Number of messages of %a %s >> ",
            msg.sender,
            messagesPerUser[msg.sender]
        );

        // emit event for web app
        emit NewMessage(msg.sender, block.timestamp, _newMessage);

        console.log("AllMessages updated");
    }
    // returns all messages
    function getAllMessages() public view returns (Message[] memory) {
        return allMessages;
    }
    // returns number of messages
    function getTotalMessages() public view returns (uint256) {
        console.log("We have %d total messages!", totalMessages);

        return totalMessages;
    }
}

It's a pretty big contract but let's review the most important parts:

  • struct Message {...}: this is a definition of the information that will be stored for each message, which will be the message itself, the address of the user that sent it and the timestamp.
  • Message[] allMessages; an array of the struct declared above in which I'll store all messages
  • event NewMessage(...): we'll use events to notify the front end that a new message was saved in the blockchain
  • function sendMessage( ... ): this is the function that stores the message in the blockchain. As you can see it adds the message to the allMessages array using the push() method, updates the counters and emits the event.
  • getAllMessages() and getTotalMessages(): this methods return the all the messages and the total number of messages.

You learn more about Solidity structs and arrays in my article about complex data types

Compile and deploy smart contract locally

Before being able to use the smart contract from our web application, we need to compile and deploy it, and for that, we need to write a script (remember the /scripts folder that hardhat created?). Create a file in /solidity/scripts/deploy.js with the following content:

const main = async () => {
  // name of the contract should match with the file name!!
  const msgContractFactory = await hre.ethers.getContractFactory(
    'MessagePortal'
  );
  const msgContract = await msgContractFactory.deploy({});

  await msgContract.deployed();

  console.log('msgPortal address: ', msgContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();

As we modified the hardhat.config.js file, Hardhat already knows where to find our contract's source code. This script is just compiling and deploy our contracts using Ethers' getContractFactory and deploy methods. After the contract is deployed, Hardhat will print a message with the blockchain address it was deployed to.

If there are errors in our contract, the compiler will throw an error and stop the script.

But where is the script going to deploy the contract to? For now, let's target a local blockchain 😉

To start an ethereum node locally, we'll run npx run node on a new terminal window (check this article on how to run an ethereum node). Once our local node is running, we can run the deploy script with npx hardhat run ./solidity/scripts/deploy.js --network localhost to compile and deploy our contract locally. Notice the --network parameter in which we can indicate the target blockchain.

We'll something more to deploy it to a test network or the Ethereum main chain, but I'll explain that in more detail later on 😉

Conclusion

In the next part of this series, I'll explain how to create the frontend using Vue.js and ethers.js, which will allow our web application to interact with the smart contract we just created. We'll also deploy the contract to the Rinkeby test network and our web app to a hosting provider to test it in a production-like environment 💪

TAGS

If you enjoyed this article consider sharing it on social media or buying me a coffee ✌️

Buy Me A Coffee