This article started as a bear market distraction for all the sad builders in the Web3 space that lost some money in the last months. Just playing around with some Web3 tech I found interesting.
After I wrote it, I got the impression it's an excellent way for Web2 devs to dip their toes into decentralized technology without being distracted by the money that usually pulls people into Web3.
There are two exciting decentralized technologies out there, and you can use both of them without cryptocurrencies: Radicle & IPFS.
This tutorial will allow you to try Web3 tech without selling your soul to the crypto gods!
Deploying a React SPA
We will deploy the default app create-react-app
emits right after initialization. You don't need to understand React for this; it should work with every other framework that can emit an SPA.
Here is the first nice thing: you can use many of your Web2 dev skills in Web3. A decentralized app (DApp) will still run in the browser like a regular web application; it will just use tech that isn't centralized.
Prerequisites
Debian/Ubuntu, Git and a current Node.js installation.
Also: No crypto wallet!
Radicle
We will start by setting up version control with Radicle, a decentralized alternative to centralized services like GitHub or GitLab.
Installing Radicle
The easiest way to install Radicle is via apt.
These commands will update your package repos and install the Radicle CLI:
$ curl https://europe-west6-apt.pkg.dev/doc/repo-signing-key.gpg | sudo apt-key add -
$ echo deb https://europe-west6-apt.pkg.dev/projects/radicle-services radicle-cli main | sudo tee -a /etc/apt/sources.list.d/radicle-registry.list
$ sudo apt update
$ sudo apt install radicle-cli
After this, you'll have a rad
command.
Creating a Radicle Identity
The idea behind Radicle is that you sign all your code with a private key. Your identity isn't bound to a central service like GitHub. You have the key; you have the identity.
Nodes make up the Radicle network. They host your repos. These nodes join and leave the Radicle network, so if you send a new update to the network, you might be talking to a different node than when you uploaded your repo for the first time.
Since you signed the code with your private key, you can tell any node that you are the actual owner of it. All a node has to do is check your signature against a public key.
So, as with any decentralized system: keep your private key safe!
This command creates your Radicle identity:
$ rad auth
Radicle differentiates between peer IDs and personal URNs because every dev might want to use different computers, which can end up with various states of the code base.
Initializing the React App
Now, we actually need a codebase we can put into Git and, in turn, onto the Radicle network. For this, we use create-react-app
, a tool that will set up a bare SPA for us.
Use this command to get a fresh React app:
$ npx create-react-app my-first-dapp
$ cd my-first-dapp
Initializing the Radicle Project
Now, we need to tell Radicle about the Git repository that create-react-app
just created for us. This will start the signing process we talked about above. You can prove your identity to the Radicle network later when you want to push updates.
$ rad init
Pushing to the Radicle Network
Next, we need to upload our fresh repo to the network, to make it available to other devs or ourselves on different devices.
$ rad push
Radicle will ask you for a preferred network, and after that, it will upload your repo. You can choose the default, which should be willow
.
If you get an ssh-agent error run
val $(ssh-agent) > /dev/null
and thenrad auth
again.
Updating Code
Now that the initial version of the codebase is on the Radicle network let's make a change and push it!
For this, replace the content of src/App.js
with the following code:
import "./App.css"
function App() {
return (
<div className="App">
<h1>My first DApp!</h1>
</div>
)
}
export default App
And run these commands:
$ git add src/App.js
$ git commit -m "Change text"
$ git push
$ rad push
To understand how these commands work together, let's look at the following image:
The first two commands add a new change to the stage and commit it with a message. Nothing special here. The change will end up in the "Local Git Repository."
The third command will push the local changes to a remote. But in the case of our Radicle setup, this isn't a remote server; it's a local server managed by Radicle. So, after this command, the change is still local to our computer.
The rad push
command now will sync our "Radicle Git Repository" with the Radicle network. This is the equivalent of git push
for centralized services like GitHub.
Cloning the Repo on Another Device
Finally, if someone wants to work on the code from another location, they can either use the Radicle web interface to get the project ID, or you can get it locally with this command:
$ rad .
It should look like this: rad:git:hnrkc...
Everyone you give this ID can clone the repo with the Radicle CLI:
$ rad clone <PROJECT_ID> --seed willow.radicle.garden
The seed can differ depending on how you initialize your repo, but the one in the example is the default.
IPFS
Now that our development setup is ready and all our fellow devs can access the code, we want to deploy the app so our users can do something with it. For this, we will deploy the code to IPFS.
Some Background on IPFS
IPFS is a protocol, just like HTTP. The difference is that IPFS URLs are hashes of the content they address; data is content addressed. Changing content also changes the address.
This allows for decentralized storage of data worldwide in the IPFS network. If someone messes with your data, the URL changes too. So, if you keep using your old URL, your data will be the same, independently of its location on the network.
But this doesn't solve the problem of persisting that data. If you have a hash to information that isn't on the network, you don't get the wrong data, but that's because you don't get any data.
To make sure the network persists your data, IPFS uses pinning services. You upload your data to a pinning service and pay it some money, so it keeps it up. There are also some free pinning services, like the decentralized one called Filecoin.
As the name implies, this service uses a cryptocurrency, but at least right now, it comes with a free storage contingent, so, again, no crypto wallet is needed.
Also, if users think your DApp is pretty cool, they might pin it on their own IPFS nodes, and it will stay online even if you aren't around anymore to maintain it.
Signup to Web3.Storage
In this tutorial, we will use Web3.Storage, a tool & service that uploads our DApp to Filecoin to make it available via IPFS.
To get started, you need to create a Web3.Storage account. While IPFS and Filecoin are decentralized, Web3.Storage itself isn't.
Creating an API Token
Next, you have to create an API token, so your deploy script can access the Web3.Storage service.
Creating the Deployment Script
Create a deploy.mjs
script inside your project directory and add the following code:
Don't forget to add the correct API key!
import { URL } from "url"
import * as path from "path"
import { getFilesFromPath, Web3Storage } from "web3.storage"
const API_TOKEN = "YOUR_API_TOKEN"
async function deploy() {
const client = new Web3Storage({ token: API_TOKEN })
const files = await getFilesFromPath(
path.join(new URL(".", import.meta.url).pathname, "build")
)
const cid = await client.put(files, { wrapWithDirectory: false })
console.log(`https://${cid}.ipfs.dweb.link/index.html`)
}
deploy()
This will connect to the Web3.Storage service, gather all files in the build
directory and then put
them (through Web3.Storage) onto the Filecoin network.
The wrapWithDirectory: false
flag ensures we find our files in the root of the URL and not wrapped with a build
directory.
In the end, the script will log an URL to an IPFS gateway that includes the CID, which is the hash of our deployed files.
But since it's decentralized, you could also use a different IPFS gateway with the same CID.
You could even run your own IPFS node locally and tell it to pin your content there. Your computer would become part of the IPFS network, and other IPFS nodes in your local network could fetch their data from there.
Building & Deploying to IPFS
Finally, the big moment. First, we have to build the React app, which will end up in the build
directory, then we have to call our script to deploy it.
$ npm run build
$ node deploy.mjs
Everything from the build
directory will be accessible via the URL logged in at the end.
The URL will look something like this:
https://<CID>.ipfs.dweb.link/index.html
The CID will change every time we do a new deployment.
Bonus: Setting a Custom Domain
Now, requiring your users to remember these big CIDs isn't good UX, nor will they know when there is a new CID after every deployment.
This is why we put a human-readable domain in front of our deployments.
You can do so by creating two records for the custom domain you own.
An A or Alias record that points to a gateway IP or domain:
209.94.90.1
A TXT record called _dnslink
that has the following content:
dnslink=/ipfs/<CID>
Your domain resolves to a gateway that can use the _dnslink
record/subdomain to resolve the data stored on IPFS. Users can open the deployed page in their browser like every other website.
Summary
Using decentralized technology isn't hard and doesn't require getting a wallet and cryptocurrency to start, not for yourself or your users.
With Radicle and IPFS, you get some decentralization benefits, like censorship resistance, without the need to go all-in.
You can run Radicle and IPFS nodes on your own hardware, in your own local networks, if you like, and other people can host your content if they think it's worth it. No single entity can delete your data from those networks.