How to architect a dApp with what you already know about web development
Hi 👋 I’m Phillip. My partner JP and I made a dApp called CryptoCare that lets users donate directly to social impact organizations in exchange for crypto-collectibles. I wanted to share what I learned with the community below. Please clap, comment, share and reach out if you have any feedback. I’d appreciate it!
About a year ago, I got very interested in building a web application that interfaces with the Ethereum blockchain. I had already done a lot of reading on decentralized networks, consensus mechanisms, the very concept of money, the joys and pitfalls of writing smart contracts, and some of the exciting user-facing projects being built using these new technologies. It was all very fascinating to me.
During this period of discovery, I remember the very first time I used a dApp. It was an incredibly simple interface, and served a very singular purpose, but it changed the way I look at the web in some fundamental ways. With this dApp I realized that without giving anyone my email address, a password, or a credit card, that I could be securely authenticated, and have the ability to spend money on the internet. I would never have to worry about the inevitable day when that information was lost, leaked, or used against my will. In addition, the data I created using the dApp was mine in a very cryptographic sense, unable to be manipulated or deleted by some central authority. For me, this was a truly powerful realization.
I wanted to be a part of it. I just needed to get my hands dirty and figure it out. So I set out to understand how to architect a dApp by building a product called CryptoCare. From this experience, I noticed that while dApps and traditional web applications differ in many ways, their ultimate structures end up being pretty similar. I’d like to share my thoughts on some of their similarities and differences.
Since the landscape surrounding this technology is always changing, this is not meant to be a manifesto of the best way to do things, but serve as a helpful guide for those who may find themselves comfortable building web applications and wanting to make the leap to using some of this decentralized technology in their next product. There are definitely pieces I would like to improve about our architecture, and am constantly thinking about how I can make things better. This article will assume you have working knowledge of things like Ethereum and IPFS, as well as experience building web applications using tools like React and Ruby on Rails.
To start, let’s look at what I think is the most basic structure of a web application. This one’s complex, so put on your thinking cap :)
There is a user that wants to access your website, so they navigate to the correct URL. A request is made to the server, which figures out what you wanted, and responds with a fully-formed HTML document to be viewed in your browser. As the user navigates around the site, requests are made to the server, which renders new HTML documents. Easy peasy.
Next let’s look at what seems to be a more mainstream way of architecting things these days:
There is a user that wants to access your website, so they navigate to the correct URL. A request is made to a server, which delivers the logic for the frontend application. Once this frontend app has been sent to the user, the frontend can make zero-to-many requests to another server whose only function is serving data (an API). This API will use tools like relational databases to store relevant data for lookup, and will be the source of truth for what data exists in this system. As the user navigates the frontend, it will retrieve what it needs from this API and display it according to its frontend rules.
This structure has many unique qualities and improvements over the first design. Separating data from how data is displayed is good in itself, but also allows for the possibility of new ways to display the data to form around the API without needing to really add much to your data layer. Some examples I’ve experienced are using the API to feed other services like a mobile app, any number of internal-facing applications for your business, or opening your data for other developers to build something around.
Both of these are very simple examples, and from my experience I know that each web application is unique and complex in their own ways. For every simple application, there are a dozen employing a unique service-oriented architecture, leveraging third-party APIs behind the scenes, and using multiple different technologies to store their data, in a wonderfully Rube-Goldberg-esque fashion. But I believe when you distill everything down to its core, most web applications follow this client-server pattern, with a frontend interfacing with an API. The API supplies data in a deterministic way, and is backed by a simple database.
But what happens when this source of truth shifts? Instead of data being stored in a queryable interface like SQL and files being stored on something like S3, the truth is now in a smart contract on the blockchain, and a file in the IPFS. How does one build a web application, used by people expecting the speed and usability we have all gotten used to on the internet, when they may not know or care about decentralization in the first place?
Before I go into how I answered some of these questions, let’s take a moment to give some context on what CryptoCare was meant to be. To start at the highest point, we wanted to build a product that brings joy to the process of giving. We wanted a platform to discover causes worth supporting. We wanted to reward users’ generous donations by issuing the user an adorable and immutable collectible representing proof of their good deed. Breaking this down a bit in terms of the most basic deliverable product:
- We needed a way to store representations of our beneficiaries (examples being GiveDirectly, Electronic Frontier Foundation, or Heifer International), their ether addresses, and other associated metadata.
- We needed a way to group these beneficiaries by a common cause.
- We wanted a way to view these causes and show information about the beneficiaries under it’s umbrella.
- Upon the user picking a beneficiary to support, we wanted an interface for users to be able to build a customizable collectible to represent their good deed.
- We needed to build the processes to create and store their collectible and route money to the beneficiary.
Looking at it in this way, I realized that the only decentralized piece was the last bullet point — the logic to store the collectible and route the money to the correct place. This led me to believe the best approach was to start with what I already knew, which is building a frontend using React which makes requests to a backend API built using Ruby on Rails, and tackling the piece that needed to be decentralized separately.
Once all of these APIs were built, the database schema in place, and the interfaces on the frontend reading everything correctly, it was time to figure out how to to tackle the decentralized bit — the donations being made and the immutable proof they had happened — and shift into the ether. So I started developing the smart contract and another backend service to help validate data. We needed them to do several things:
- Given an amount to be donated, a cause, and a configuration of the desired collectible, we needed to generate an image representing the collectible.
- Once this image was generated, we needed to store it with a lot of other metadata about the donation and the collectible, in IPFS. Storing this data in IPFS would mean that even if CryptoCare eventually went away, users would have the ability to take what was created with them. We also wanted the format of the data to be consumable to Opensea.
- Once this data had been stored in IPFS, we needed to store a reference of this collectible in an ERC721 smart contract. Being an ERC721, this would be truly ownable by the user, giving them access to transfer it, and making it available to in other dapps without needing CryptoCare’s permission.
- We needed the smart contract to route the amount of ether specified by the user to the correct address. Using ether as the mode of money transfer vastly simplifies a very complex problem that arises using credit card processors and bank accounts, while keeping the ability for these donations to remain tax-deductible. We wanted the transmission of money to be instantaneous, never being in CryptoCare’s control. We wanted to open-source this piece so everyone could clearly verify the money was going where we said it was going.
- We needed to validate that people minting new CryptoCare tokens were going through the above process, and not able to store bad data in the smart contract.
- Upon a successful token minting, we would need to store a reference in our Rails app such that an API could quickly query for it. In this case, the API isn’t the final source of truth for the donation, but merely what I think of as a cache layer atop IPFS + Ethereum. Our API is faster to query, which is good for building a web app, but since the data is available to anyone in these decentralized networks, someone could build their own API around it if they so desired.
- Any changes to the data in the smart contract (Ex: a user wants to transfer their collectible to another address, an edit is made to the collectible’s name) would need to be accounted for and updated in the rails API.
So now our diagram balloons a little bit to this:
While it’s quite a bit more complex than your vanilla web stack, a lot of the pieces are still very recognizable. The main difference is shifting the source of truth for your data when it makes sense to decentralize that data and using what you know already for most of the other pieces.
I open-sourced the smart contracts that power the CryptoCare platform, if you want to get in the weeds of how it all works. I would like to eventually speak more in depth on how I solved the problems of interfacing with the blockchain from the frontend, validating the data being passed to the smart contract, and watching for updates on-chain to update the API’s data, but first wanted to start with a very high-level overview of how I approached building my first dApp coming from a web development background.
I’d like to make some shout-outs to some of the resources and people making development on these platforms easier, and giving me the ability to make a product like this. First of all, Consensys, and their products Infura, Metamask, and Truffle were indispensable. I took the Consensys Academy course over the summer as well, which helped me level up quite a bit. I also couldn’t have built it without the work being done within OpenZeppelin and all of the open-sourced, audited smart contracts they produce. The amount of free or open-source tooling in this space is quite remarkable and I appreciate all they have produced.
If you enjoyed this, please:
Follow CryptoCare, myself, and my partner JP on twitter, learn more about why we started the project, and visit CryptoCare and support a cause you believe in! ✌️🌍💕