Introduction Smart contract development and the Web3 economy are still in their infancy, and there is a high demand for blockchain developers globally. According to a recent , job postings containing terms like Bitcoin, blockchain, and other digital asset-related roles increased 395 percent in 2021 in the United States compared to the previous year. With a 98 percent increase, this was the fastest growing industry in history, outpacing the entire industry by four times. LinkedIn report Blockchain development has a greater chance of landing you that six-figure dream job of yours. The coolest part is that most blockchain jobs are remote-based, so you don’t need to worry about locations. But how do you jump in and capitalize on this space? It's by learning how to build things with technology. If you are looking for a personal tutor to speed you up with web3 development, kindly . book a session with me With that being said, let’s jump into this tutorial… Why Should You Master CRUD Functions in Solidity? Mastering how to create, read, update, and delete records from the blockchain is a skill you must understand. Yes, you can delete stuff from the blockchain (to an extent) using the technique you are about to learn. CRUD skills are needed to effectively build a content-driven system, and being that the blockchain serves as a database to immutably store records, wisdom demands that you understand how to work with the technology. Again, if you’re going to work on a blockchain project for either a company or a client, there may still be a need to remove some unnecessary records which is why you should your Solidity CRUD skills now. ACE What if you’re working on a web3 blog project, and you know there needs to be a delete button for very bad comments? You will need to delete functionality in that case. You see that a good knowledge of CRUD will become inevitable as you dive deep into blockchain development. By the way, deleting a record from a blockchain network is kind of different from the way you delete it from a regular database. The record will still exist on the network but will be removed from your web3 app. The full example below will show you step by step how to accomplish this. CRUD Functions Example This is a smart contract that I created and tested; to see how it works, you can open it in a remix editor and run it yourself. But first, finish the tutorial; there is important information below that explains each function specifically. //SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Blog { address public owner; uint256 public activePostCounter = 0; uint256 public inactivePostCounter = 0; uint256 private postCounter = 0; mapping(uint256 => address) public delPostOf; mapping(uint256 => address) public authorOf; mapping(address => uint256) public postsOf; enum Deactivated { NO, YES } struct PostStruct { uint256 postId; string title; string description; address author; Deactivated deleted; uint256 created; uint256 updated; } PostStruct[] activePosts; PostStruct[] inactivePosts; event Action ( uint256 postId, string actionType, Deactivated deleted, address indexed executor, uint256 created ); modifier ownerOnly(){ require(msg.sender == owner, "Owner reserved only"); _; } constructor() { owner = msg.sender; } function createPost( string memory title, string memory description ) external returns (bool) { require(bytes(title).length > 0, "Title cannot be empty"); require(bytes(description).length > 0, "Description cannot be empty"); postCounter++; authorOf[postCounter] = msg.sender; postsOf[msg.sender]++; activePostCounter++; activePosts.push( PostStruct( postCounter, title, description, msg.sender, Deactivated.NO, block.timestamp, block.timestamp ) ); emit Action ( postCounter, "POST CREATED", Deactivated.NO, msg.sender, block.timestamp ); return true; } function updatePost( uint256 postId, string memory title, string memory description ) external returns (bool) { require(authorOf[postId] == msg.sender, "Unauthorized entity"); require(bytes(title).length > 0, "Title cannot be empty"); require(bytes(description).length > 0, "Description cannot be empty"); for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { activePosts[i].title = title; activePosts[i].description = description; activePosts[i].updated = block.timestamp; } } emit Action ( postId, "POST UPDATED", Deactivated.NO, msg.sender, block.timestamp ); return true; } function showPost( uint256 postId ) external view returns (PostStruct memory) { PostStruct memory post; for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { post = activePosts[i]; } } return post; } function getPosts() external view returns (PostStruct[] memory) { return activePosts; } function getDeletedPost() ownerOnly external view returns (PostStruct[] memory) { return inactivePosts; } function deletePost(uint256 postId) external returns (bool) { require(authorOf[postId] == msg.sender, "Unauthorized entity"); for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { activePosts[i].deleted = Deactivated.YES; activePosts[i].updated = block.timestamp; inactivePosts.push(activePosts[i]); delPostOf[postId] = authorOf[postId]; delete activePosts[i]; delete authorOf[postId]; } } postsOf[msg.sender]--; inactivePostCounter++; activePostCounter--; emit Action ( postId, "POST DELETED", Deactivated.YES, msg.sender, block.timestamp ); return true; } function restorDeletedPost( uint256 postId, address author ) ownerOnly external returns (bool) { require(delPostOf[postId] == author, "Unmatched Author"); for(uint i = 0; i < inactivePosts.length; i++) { if(inactivePosts[i].postId == postId) { inactivePosts[i].deleted = Deactivated.NO; inactivePosts[i].updated = block.timestamp; activePosts.push(inactivePosts[i]); delete inactivePosts[i]; authorOf[postId] = delPostOf[postId]; delete delPostOf[postId]; } } postsOf[author]++; inactivePostCounter--; activePostCounter++; emit Action ( postId, "POST RESTORED", Deactivated.NO, msg.sender, block.timestamp ); return true; } } Alright, let's dissect this smart contract step by step; if you're a beginner, don't worry; I wrote this smart contract example to demonstrate simplicity. The smart contract structure Step 1: //SPDX-License-Identifier: MIT pragma solidity ^0.8.7; contract Blog { // code goes here... } This is a typical smart contract structure; consider it a class in object-oriented programming because it is one in reality. The SPDX is used to specify the smart contract's license type. Pragma refers to the version of the solidity compiler that will be used for the smart contract. Finally, the smart contract's name is Blog. It is critical to note that the file name should always correspond to the class name; this will ensure consistency and prevent unnecessary bugs in your code. Defining Contract Variables Step 2: address public owner; uint256 public activePostCounter = 0; uint256 public inactivePostCounter = 0; uint256 private postCounter = 0; Here we have two types of variables: address and uint256. An address represents a user's account, whereas uint256 represents an unsigned integer ranging from 0 to 2256–1. The owner will hold the deployer’s account and holds a number of available posts. keeps track of the deleted post, just in case, it needs to be recovered. holds the total number of posts in the smart contract, but it is private. activePostCounter inactivePostCounter postCounter Defining the Mappings Step 3: mapping(uint256 => address) public delPostOf; mapping(uint256 => address) public authorOf; mapping(address => uint256) public postsOf; is a mapped variable that accepts a and returns the address of the post author. Whereas keeping track of an author’s posts, and delPostOf() serves as a recycling bin for each author. authorOf() postId postsOf() Defining an Enumerable Step 4: enum Deactivated { NO, YES } This is used to indicate whether or not a post has been deleted. Enum converts an alphabetical value to unsigned integers beginning with zero. For example, the values NO and YES would become 0 and 1, respectively. Defining the Struct Step 5: struct PostStruct { uint256 postId; string title; string description; address author; Deactivated deleted; // We are using the enum here uint256 created; uint256 updated; } PostStruct[] activePosts; PostStruct[] inactivePosts; Structs are used to define complex data structures, which are made up of two or more data types. We specified in the above that a post should have an Id, title, description, author, a deleted key, time of creation, and time of update. PostStruct Next, we made two arrays: one for active posts and one for inactive posts or deleted posts. This method prevents data loss, but you can also use a single array and only toggle the deleted key on and off. However, this raises another issue: you'll have to read them all in your frontend app before showing the active posts. I'd like to spare you that trip. Defining an Event Step 6: event Action ( uint256 postId, string actionType, Deactivated deleted, address indexed executor, uint256 created ); We defined a dynamic event to emit some vital information on created, updated, deleted and restored functions respectively. Defining modifier Step 7: modifier ownerOnly(){ require(msg.sender == owner, "Owner reserved only"); _; } A modifier is a function that changes the behavior of another function at compile time. We can say, it is a prerequisite for executing a function. In the above modifier, we specified that the caller of the function to which this modifier will be attached must be the owner or deployer of the smart contract. Defining the Constructor Step 8: constructor() { owner = msg.sender; } Based on your OOP knowledge, a constructor is always the first function to run when a class is instantiated. OOP construction is mutual to smart contracts. The first function to run when this smart contract is deployed is the constructor, and we specified that it should make the owner the one who deployed the smart contract. The function Step 9: createPost() function createPost( string memory title, string memory description ) external returns (bool) { // Checks for empty string require(bytes(title).length > 0, "Title cannot be empty"); require(bytes(description).length > 0, "Description cannot be empty"); // Performs computations postCounter++; authorOf[postCounter] = msg.sender; postsOf[msg.sender]++; activePostCounter++; // Adds post to array activePosts.push( PostStruct( postCounter, title, description, msg.sender, Deactivated.NO, block.timestamp, block.timestamp ) ); // Emits a created event emit Action ( postCounter, "POST CREATED", Deactivated.NO, msg.sender, block.timestamp ); // Returns success return true; } This function accomplishes the post-creation in five steps. Firstly, it checks that you are sending real information to the smart contract and not just an empty string. Secondly, it performs some computations like incrementing post counts for the blog and for the author, including making the author the post owner. Third, it adds the post into the using the defined earlier. activePosts PostStruct Fourthly, it emits a “POST CREATED” event which is logged off on the EVM, this is essential for enriching the information captured in a transaction. Lastly, we returned a true boolean key indicating that the entire function ran successfully. The function. Step 10: updatePost() function updatePost( uint256 postId, string memory title, string memory description ) external returns (bool) { // Checks for empty string require(authorOf[postId] == msg.sender, "Unauthorized entity"); require(bytes(title).length > 0, "Title cannot be empty"); require(bytes(description).length > 0, "Description cannot be empty"); // Changes post record for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { activePosts[i].title = title; activePosts[i].description = description; activePosts[i].updated = block.timestamp; } } // Emits a updated event emit Action ( postId, "POST UPDATED", Deactivated.NO, msg.sender, block.timestamp ); // Returns success return true; } This function operates like a function. The difference is that when checking for an empty string, the function ensures that the person updating the function is the post owner (author). createPost() updatePost() Next, it targets the particular post by the Id and updates the record. The function Step 11: showPost() function showPost( uint256 postId ) external view returns (PostStruct memory) { PostStruct memory post; for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { post = activePosts[i]; } } return post; } This simply returns to our post by recursively searching for a match in the array. activePost The function. Step 12: getPosts() function getPosts() external view returns (PostStruct[] memory) { return activePosts; } Returns all available active blog posts. The function Step 13: getDeletedPost() function getDeletedPost() ownerOnly external view returns (PostStruct[] memory) { return inactivePosts; } This simply returns all deleted posts and can only be accessed by the owner of the blog. The function Step 14: deletePost() function deletePost(uint256 postId) external returns (bool) { // check if post belong to user require(authorOf[postId] == msg.sender, "Unauthorized entity"); // find and delete post for(uint i = 0; i < activePosts.length; i++) { if(activePosts[i].postId == postId) { activePosts[i].deleted = Deactivated.YES; activePosts[i].updated = block.timestamp; inactivePosts.push(activePosts[i]); delPostOf[postId] = authorOf[postId]; delete activePosts[i]; delete authorOf[postId]; } } // Recallibrate counters postsOf[msg.sender]--; inactivePostCounter++; activePostCounter--; // Emits event emit Action ( postId, "POST DELETED", Deactivated.YES, msg.sender, block.timestamp ); // Returns success return true; } The above function was achieved in four steps. First, we validated that the owner of the post is the one deleting, then we searched for the post, deleted it, and emitted a “POST DELETED” event which also returns a boolean value of true. Please take note that delete behavior is not done like in a normal database, rather we specified two arrays; and . Whenever we deleted a post, it will be moved to the array, and only the blog owner can restore it at the request of the post author. activePosts inactivePosts inactivePosts The Post Restore Function Step 15: function restorDeletedPost( uint256 postId, address author ) ownerOnly external returns (bool) { // checks if post exists in users recycle bin require(delPostOf[postId] == author, "Unmatched Author"); // Finds and restore the post to the author for(uint i = 0; i < inactivePosts.length; i++) { if(inactivePosts[i].postId == postId) { inactivePosts[i].deleted = Deactivated.NO; inactivePosts[i].updated = block.timestamp; activePosts.push(inactivePosts[i]); delete inactivePosts[i]; authorOf[postId] = delPostOf[postId]; delete delPostOf[postId]; } } // Recallibrates counter to reflect restored post postsOf[author]++; inactivePostCounter--; activePostCounter++; // Emits a restoration event emit Action ( postId, "POST RESTORED", Deactivated.NO, msg.sender, block.timestamp ); // resturns success return true; } Whenever a post is restored, it is moved from the array to the array. inactivePosts activePosts It is worth mentioning that you can design your delete pattern a little bit differently than mine. You can decide to leverage the enum Deactivated structure we created above and only delete it by toggling the deleted state to YES or NO. Men and brethren, that is how you crush the solidity CRUD functions… Conclusion It’s been a blast journeying with you in this tutorial, I hope you got some valuable insights on how to write solidity smart contract CRUD functions. I want to inform you of my private tutoring classes for those who want to jump into the web3 space. If you’re interested, please . book me up on my website That’s all for this tutorial, please like and share… Excited to see you in the next tutorial, till then, enjoy! About the Author Gospel Darlington kick-started his journey as a software engineer in 2016. Over the years, he has grown full-blown skills in JavaScript stacks such as React, ReactNative, NextJs, and now blockchain. He is currently freelancing, building apps for clients, and writing technical tutorials teaching others how to do what he does. Gospel Darlington is open and available to hear from you. You can reach him on , , , or on his . LinkedIn Facebook Github website