How to Build a CRUD App With Solidity

How to Build a CRUD App With Solidity

Have you ever wondered how to create your blockchain application? When it comes to Ethereum, it starts with smart contracts.

In this article, we will learn how to build a simple, smart contract on Ethereum and test it using the Truffle framework. Our smart contract will perform essential create, read, update, and delete (CRUD) operations.

We'll focus on smart contracts written in Solidity language. We'll use the Truffle Suite to deploy a local version of the Ethereum blockchain and compile smart contracts using Ethereum Virtual Machine (EVM).

Prerequisites

For this tutorial, the following software and packages are required:

  • Node and its package manager, npm. We run the command node -v && npm -v to verify we have them installed, or install them from here
  • Alternatively, we can use another package manager, Yarn
  • An Ethereum blockchain, a smart-contract compiler
  • A JavaScript library for communication, Web3.js

What is a smart contract?

In simple words, A smart contract is a piece of code that controls some digital asset. It defines rules for transferring assets and penalties like a traditional contract. The best thing is that it automatically performs these transfers and penalties based on pre-coded conditions without needing an intermediary.

What is Solidity?

Solidity is one of the most famous languages to code smart contracts on Ethereum. It is designed for smart contract programming. It's syntactically similar to javascript.

What is the Truffle Suite?

Ethereum is a blockchain that allows applications to run on it. The code is written in Solidity language in the form of smart contracts. To compile these contracts, we need an Ethereum compiler, which converts smart contracts to machine-readable code.

The Truffle Suite is a collection of tools made specifically for blockchain development on Ethereum. The suite includes three pieces of software:

  • Truffle, a framework for smart contract development
  • Ganache, which enables you to set a personal Ethereum blockchain on the local network for testing and development
  • Drizzle, which is used for creating DApp user interfaces and includes a collection of ready-to-use components

trufflesuite-logo.png

Installing Ganache

The smart contracts run on the Ethereum blockchain, so we require one for deployment and testing. We could also deploy on a live chain, but that would cost us Ether as a gas fee. So let's set up a local chain and do our testing there. When you're sure about the code and ready to distribute your application, you can deploy it on the live chain.

Ganache is the local chain installed on our computers and runs on localhost. Download Ganache from the Truffle Suite website.

genache.png

We can see that Ganache provided ten accounts with 100 ETH each. These are fake Ethers, so don't be too excited. Also, the chain is running on 127.0.0.1 at 7545 port. We will use these accounts to deploy our smart contracts on this chain. Ethers will help us pay gas fees.

Installing Truffle

Truffle provides the compiler for smart contracts. We need it to convert the Solidity code into machine-readable code that can be deployed on the Ganache blockchain.

Install Truffle using the following command:

 $ npm install truffle -g

Yarn:

 $ yarn add truffle

Creating smart contracts

To create smart contracts, we first need to create a project directory where we will keep all the Solidity files. Let's create one with the name crud-app and move to the directory in the terminal using cd crud-app.

Right now, our project is empty. To work with it, we need some boilerplate code. For example, if we wish to create the UI in React, we'll need to install React.

Truffle already provides some packages called boxes. These packages are bundles of different frameworks, such as Truffle, Ganache, React, Web3, and Redux, and there is one for Vue.js developers. Together, they complete the end-to-end application development, from client UI to blockchain smart contracts.

This article will use the React box provided by Truffle.

Installing the React box

To install the React box, run the following command:

yarn truffle unbox react

This will install Web3.js, React, Ganache CLI, Truffle, and Ethereum.

For this tutorial, we won't focus on React or a browser-based UI. Instead, we'll create the smart contracts and handle them with the terminal only.

The directory structure of our project will look like this:

image.png

Here, the client is a React project folder where we can create the UI of the application. A folder inside it client/src/contracts holds the compiled smart contracts in JSON format. These files are generated when we compile our smart contracts. They contain the ABI, bytecode, and other information.

{
  "contractName": "Migrations",
  "abi": [
    {
      "inputs": [],
      "name": "last_completed_migration",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function",
      "constant": true
    },
    {
      "inputs": [],
      "name": "owner",
      "outputs": [
        {
          "internalType": "address",
          "name": "",
          "type": "address"
        }
      ],
      "stateMutability": "view",
      "type": "function",
      "constant": true
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "completed",
          "type": "uint256"
        }
      ],
      "name": "setCompleted",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ],
  "metadata": "{\"compiler\":{\"version\":\"0.8.11+commit.d7f03943\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"last_completed_migration\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"completed\",\"type\":\"uint256\"}],\"name\":\"setCompleted\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"project:/contracts/Migrations.sol\":\"Migrations\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"project:/contracts/Migrations.sol\":{\"keccak256\":\"0x7eaedbb1a3e4e0f585d9063393872f88ded247ca3c3c3c8492ea18e7629a6411\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4a3eb571cee910095df65a06a1c1d3f89187c72a3c184ef87a7538d9aa39ad07\",\"dweb:/ipfs/QmdqR3vrSSGR49qFGZr49Mb39z7dgD6tSzEDoaqtM31o61\"]}},\"version\":1}",
  "bytecode": "0x6080604052336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561005057600080fd5b50610327806100606000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd57614610082575b600080fd5b61004e61009e565b60405161005b9190610179565b60405180910390f35b61006c6100a4565b60405161007991906101d5565b60405180910390f35b61009c60048036038101906100979190610221565b6100c8565b005b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014d906102d1565b60405180910390fd5b8060018190555050565b6000819050919050565b61017381610160565b82525050565b600060208201905061018e600083018461016a565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101bf82610194565b9050919050565b6101cf816101b4565b82525050565b60006020820190506101ea60008301846101c6565b92915050565b600080fd5b6101fe81610160565b811461020957600080fd5b50565b60008135905061021b816101f5565b92915050565b600060208284031215610237576102366101f0565b5b60006102458482850161020c565b91505092915050565b600082825260208201905092915050565b7f546869732066756e6374696f6e206973207265737472696374656420746f207460008201527f686520636f6e74726163742773206f776e657200000000000000000000000000602082015250565b60006102bb60338361024e565b91506102c68261025f565b604082019050919050565b600060208201905081810360008301526102ea816102ae565b905091905056fea2646970667358221220353cb1298ecaaf65fe00ddfd9e11ec1e26a6b97a78dc65de1604cb8b8a399ab364736f6c634300080b0033",
  "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c8063445df0ac146100465780638da5cb5b14610064578063fdacd57614610082575b600080fd5b61004e61009e565b60405161005b9190610179565b60405180910390f35b61006c6100a4565b60405161007991906101d5565b60405180910390f35b61009c60048036038101906100979190610221565b6100c8565b005b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161014d906102d1565b60405180910390fd5b8060018190555050565b6000819050919050565b61017381610160565b82525050565b600060208201905061018e600083018461016a565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006101bf82610194565b9050919050565b6101cf816101b4565b82525050565b60006020820190506101ea60008301846101c6565b92915050565b600080fd5b6101fe81610160565b811461020957600080fd5b50565b60008135905061021b816101f5565b92915050565b600060208284031215610237576102366101f0565b5b60006102458482850161020c565b91505092915050565b600082825260208201905092915050565b7f546869732066756e6374696f6e206973207265737472696374656420746f207460008201527f686520636f6e74726163742773206f776e657200000000000000000000000000602082015250565b60006102bb60338361024e565b91506102c68261025f565b604082019050919050565b600060208201905081810360008301526102ea816102ae565b905091905056fea2646970667358221220353cb1298ecaaf65fe00ddfd9e11ec1e26a6b97a78dc65de1604cb8b8a399ab364736f6c634300080b0033",
  "immutableReferences": {},
  "generatedSources": [],
  "deployedGeneratedSources": [
    {
      "ast": {
        "nodeType": "YulBlock",
        "src": "0:3176:2",
        "statements": [
          {
            "body": {
              "nodeType": "YulBlock",
              "src": "52:32:2",
              "statements": [
                {
                  "nodeType": "YulAssignment",
                  "src": "62:16:2",
                  "value": {
                    "name": "value",
                    "nodeType": "YulIdentifier",
                    "src": "73:5:2"
    }

As seen above, client/src/contracts is set to hold compiled code. The development network is set at port 8545. This is the port where this box is running Ganache.

If you look at the top where we installed Ganache, you can see that it was running at 7545 port, but this one is running at 8545 because 7545 is already used by our installed Ganache. If you wish, you can change this port to 7545, and Truffle will use the Ganache and the accounts we installed instead of the one provided by the box. I am keeping it at 8545.

Building a smart contract with CRUD operations

Now it's time to write some code. We will do CRUD operations and manage a list of technologies.

Our application will show a list of different technologies. You can add, update, and delete the technologies.

If you've developed apps before, we'll follow a procedure you're undoubtedly familiar with:

  • Create an array to hold the names of technologies
  • Create a function to push a new value to the array
  • Create a function to change the value at a given index
  • Create a function to delete a value
  • Create a function to return the array

Now let's write the code in Solidity.

Create a new file in the contracts directory and call it Techs.sol. We'll start the file by indicating the license and solidity version we are supporting:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

Next, declare the contract scope where we are going to write all the code:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{

}

Create an array to hold the tech stacks:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{
    string [] myTechs;
}

This is a string array with a private modifier, which means it can't be accessed outside the contract; thus, we can't change the value directly.

Next, create a function to add a new value:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{
    string [] myTechs;

    function addTech(string memory techName) public {
      myTechs.push(techName);
  }
}

In the code snippet above, we created a function called addTech, which accepts a string as a parameter, techName. This is declared public, so the UI or terminal can call it. In the function body, we simply push the value to the array.

Update the value:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{
    string [] myTechs;

   function addTech(string memory techName) public {
       myTechs.push(techName);
   }

  function updateTech(uint techIndex, string memory newTechName) public returns (bool) {
      if(myTechs.length > techIndex){
          myTechs[techIndex] = newTechName;
          return true;
      }
      return false;
  }
}

updateTech accepts two arguments, techIndex and newTechName, and returns a boolean value. It works like this: if the index is out of the array's bounds, it returns false. Otherwise, it changes the value of the array with a new provided fruit name at the provided index and returns true.

The next step is to create the delete function:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{
    string [] myTechs;

    function addTech(string memory techName) public {
      myTechs.push(techName);
  }

  function updateTech(uint techIndex, string memory newTechName) public returns (bool) {
      if(myTechs.length > techIndex){
          myTechs[techIndex] = newTechName;
          return true;
      }
      return false;
  }

  function deleteTech(uint techIndex) public returns (bool) {
      if(myTechs.length > techIndex){
          for(uint i=techIndex; i < myTechs.length-1; i++){
              myTechs[i] = myTechs[i+1];
          }

          myTechs.pop();

          return true;
      }
      return false;
  }
}

Here we check the index out of bounds condition and then update the array by replacing the value with the next value from the provided index. This way, the value of the provided index will be lost. Ultimately, we pop out the last value and return true.

The last step is to return the array. To read all the values of the array:

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract Techs{
    string [] myTechs;

    function addTech(string memory techName) public {
      myTechs.push(techName);
  }

  function updateTech(uint techIndex, string memory newTechName) public returns (bool) {
      if(myTechs.length > techIndex){
          myTechs[techIndex] = newTechName;
          return true;
      }
      return false;
  }

  function deleteTech(uint techIndex) public returns (bool) {
      if(myTechs.length > techIndex){
          for(uint i=techIndex; i < myTechs.length-1; i++){
              myTechs[i] = myTechs[i+1];
          }

          myTechs.pop();

          return true;
      }
      return false;
  }

  function getTechs() public view returns (string[] memory) {
      return myTechs;
  }
}

Compiling smart contracts using Truffle

Now that we've finished coding our smart contract, it's time to compile it using Truffle. But first, we need to create a migration file to indicate to Truffle that we want to migrate this to the chain.

If we check in the migration folder, we’ll find a JavaScript file:

migration.png

The file starts with a number, so our second file will begin with 2, and so on. The code is nearly standard. For 1_initial_migrations it is:

const Migrations = artifacts.require("Migrations");

module.exports = function (deployer) {
  deployer.deploy(Migrations);
};

Let's add our migration file, 2_techs_contracts:

const TechStack = artifacts.require("./Techs.sol");

module.exports = function (deployer) {
  deployer.deploy(TechStack);
};

We're ready to compile and migrate our techs contract. Move to the terminal and run the following:

> truffle develop

truffle-develop.png

This command will start the truffle console. It will also show information such as the chain network, accounts, Mnemonic, etc.

Ganache provides ten accounts by default. They will be different for you. Please don't use any of these private keys on the live chain because they are visible to all the visitors of this article, meaning anybody can access these accounts.

Now, let's compile the contracts using this command:

> compile

truffle-migration.png

We can check in build/contracts whether a Techs.json file is created or not.

We can migrate the compiled file to the chain using this command:

> migrate

This will migrate all three smart contracts to the chain.

image.png

image.png

image.png

Finally, our application is on the Ethereum chain. We spent 0.001778438 ETH on gas fees, and the transactions happened from the first account. By default, it always takes the first account. We can perform various operations now.

Getting a List of Technologies

We can use Web3.js to react and write various values. Let's first store the instance of our contract in a variable:

let instance = await Techs.deployed()

We are using await because everything in the blockchain is asynchronous and returns a promise.

Now use this instance to get the array:

> let techs = instance.getTechs()
undefined
> techs
[]

This will return an empty array because our techs array currently has no value.

addTech.png

Adding a Technology to the list

Let's add some technologies:

> let result = await instance.addTech("JavaScript")
undefined

The result will hold the transaction. This operation adds value to the array and hence changes the data. It is, therefore, recorded as a transaction. Remember, all the read operations are free, but any operation that leads to a change in the blockchain is subject to a gas fee.

image.png

Now we can again read the array to check the content:

getTech.png

Let's add a few more techs to the list:

await instance.addTech("React");
await instance.addTech("Nextjs");
await instance.addTech("Web3.js");
await instance.addTech("Solidity");

Remember, all these operations will cost you Ether. You can save the fee by creating a function in your contract for accepting multiple fruit values at a time.

Read the array now:

image.png

Updating a Technology name

We can see in the above image that "React.js" was written as "React." Let’s correct it using the updateTech() function. It will accept the index and new value. The index is 1.

> await instance.updateTech(1, "React.js")

Let’s read the array now:

updateTech.png

The spelling is successfully fixed.

Deleting a Technology name

The last operation is to delete a value:

> await instance.deleteTech(5)

Read the values:

deleteTech.png

The "Svelte" item has been deleted from the list.

Conclusion

Creating smart contracts and deploying on the blockchain is fun and powerful. It gives a new perspective from traditional programming. You can create all sorts of applications, such as online voting, digital bank, wallets, auctions etc., using these techniques.

In this tutorial, we demonstrated how to build a CRUD app with solidity and deploy it on local chain.

To learn more about NFTs, dApps, the blockchain, and other web3 content, check out Hashnode’s web3 blog.