I wouldn’t call myself a serious developer anymore, but I used to be, and I still love creating programs. So I took Holochain’s new scaffolding tool for a spin, just to see what it’s like. And I did it — I made my first-ever hApp, and it works! It’s not pretty, and it took more like a couple hours to think out my design, download packages, and get familiar with the tool, but on a subsequent try I was able to speed-run the scaffolding in under 10 minutes.
[Editor’s note: this guide is meant for Holochain 0.1. When a new version with breaking changes is released, we’ll update this guide shortly afterwards.]
Now, I have been breathing Holochain for the past five years, so I have a decent understanding of how it works. I’ll be honest, it takes effort for us to break free of the centralised mindset. But your first decentralised app doesn’t have to be perfect — all that matters is that it works. The beauty of this tool is that it helps you get there quickly.
Colleagues and friends in the Holochain world have created a few scaffolding tools for various iterations of Holochain over the years. Many of them were visual, allowing non-developers to easily prototype a hApp without having to write code.
This one is different: it’s entirely CLI-driven. While we still believe it’s crucial to empower everyone to create a hApp, we recognise that current reality means this stuff is still mostly the domain of developers. So, after receiving a lot of feedback, the new scaffolding tool has been designed to support them first.
I remember watching a video in which Guillem Cordoba, the creator of the new scaffolder, demoed it. I could see the excitement in the faces of the other developers — Eric Harris-Braun, co-creator of Holochain and himself a prolific hApp dev, later said this:
For people who have been in the web app development world for a while, you might remember when Rails came out with its scaffolding, how much of a game changer that was for building Web2 apps—the way you could scaffold an app that runs in literally ten minutes. And what we’ve got coming out will be, I believe, just as powerful for Web3 apps. That gives developers a super powerful ability to just get started.
And like the Rails scaffolder, I see it as being just as useful for experienced hApp devs as for new ones. We all find ourselves writing the same boilerplate code over and over again, and that’s a sign that that stuff should be automated.
So what can we do with this thing? Let’s find out!
Creating a gardeners’ swap hApp
A little aside: My Holochain journey started back in 2017, in a vast expanse of rocks and weeds. I’d been tasked with turning it into a community garden space, and I was looking for software that would help gardeners track their participation in shared projects such as walnut trees and ‘squash jungles’.
I never did find what I was looking for. Instead, I found something weird and wonderful: a group of folks creating an internet protocol that works like a community garden. Years later, I’m still hanging around those folks, and I’m even starting to see them create the beginnings of the kind of software I’d imagined all those years ago.
That garden management hApp is probably out of my reach, so I’ll try building something smaller: a garden swap. People can use it to list extra seeds, plants, and produce they have available or are looking for.
Getting set up
I used the instructions on the Developer Portal to install the Holochain dev environment. (You can read more about it in the first article.) After that’s set up, I scaffold an empty project:
$ nix run github:holochain/holochain#hc-scaffold -- web-app gardenswap
What I see is immediately comforting; it looks like it’s going to guide me through the process with a bunch of questions:
? Choose UI framework: ›
Vue
Svelte
❯ Lit
I’ll go with Lit, which adds the bare minimum needed to make the DOM dynamic, because that seems old-fashioned — like me.
? Do you want to set up the holonix development environment for this project? ›
❯ Yes (recommended)
No
In the previous article we learned that Holonix lets us create and share reproducible development environments; this step is going to make sure that the scaffolded code has all the right runtime dependencies alongside it.
Now I see a bunch of gobbledigook, followed by this cheerful message and a bunch of help text:
Web hApp "gardenswap" scaffolded!
Notice that this is an empty skeleton for a Holochain web-app, so:
- It won't compile until you add a DNA to it, and then add a zome to that DNA.
- The UI is empty, you'll need to import the appropriate components to the top level app component.
Set up your development environment with:
cd gardenswap
nix develop
npm install
Then, add new DNAs to your app with:
hc scaffold dna
That sounds easy enough. I’m going to follow the instructions above; the nix develop
command is what gets me into the Holonix development environment by looking for a file called flake.nix
in the project directory.
paul@panda:~/Holochain$ cd gardenswap
paul@panda:~/Holochain/gardenswap$ nix develop
Scaffolding some code
Now I’m in and it’s time to start generating code. It told me to just type in hc scaffold dna
, but I’m going to save a step by adding the DNA name to the end of that command. This is a simple hApp, only one DNA with the same name as the hApp itself:
[nix-shell:~/Holochain/gardenswap]$ hc scaffold dna gardenswap
DNA "gardenswap" scaffolded!
Add new zomes to your DNA with:
hc scaffold zome
And now it’s time to scaffold a ‘zome’ — a module to hold my actual code. Again, it’s just a simple DNA with only one zome (actually two — more on that in a moment):
[nix-shell:~/Holochain/gardenswap]$ hc scaffold zome gardenswap
? What do you want to scaffold? ›
❯ Integrity/coordinator zome-pair (recommended)
Only an integrity zome
Only a coordinator zome
It’s nice of the scaffolder to recommend a choice to me. I’ll go with that.
Now it asks me to confirm the folder name choices — just in case there’s something already there, I guess.
✔ Scaffold integrity zome in folder "dnas/gardenswap/zomes/integrity/"? · yes
Integrity zome "gardenswap_integrity" scaffolded!
✔ Scaffold coordinator zome in "dnas/gardenswap/zomes/coordinator/"? · yes
Coordinator zome "gardenswap" scaffolded!
Here’s creating two zomes — an integrity zome and a coordinator zome. The first one defines data types and validation rules for them, and the second one defines API functions for working with the data.
Now it’s doing some Rust stuff — downloading crates, that sort of thing. Finally it gives me the next step — to start adding entry definitions to my zome:
Add new entry definitions to your zome with:
hc scaffold entry-type
The first type I’m going to scaffold, the thing that the whole app revolves around, is the listing. Offers and requests will look pretty similar, so we’ll use the same type for both, along with a flag to indicate which one it is.
[nix-shell:~/Holochain/gardenswap]$ hc scaffold entry-type listing
First it asks me to choose a data type for my field. I’m vaguely familiar with Rust and Holochain types already, so I kinda get what these all mean. I’m going to start off with a field pointing back to the person who created the listing.
Which fields should the entry contain?
? Choose field type: ›
String
bool
u32
i32
f32
Timestamp
ActionHash
EntryHash
DnaHash
❯ AgentPubKey
Enum
Option of...
Vector of...
Next it offers to generate code that creates links from people to the listings they create — because a Holochain DHT is a graph database, not an SQL database, you always have to explicitly create both sides of a relationship, either as fields or links.
✔ Should a link from this field be created when this entry is created? · yes
Now it asks me to describe the relationship between the agent and the listing. I call it ‘creator’:
✔ Which role does this agent play in the relationship ? (eg. "creator", "invitee") · creator
Finally, it asks me to name the field. I go with the recommended name, which came from my previous answer:
✔ Field name: · creator
Now I start the process again, for the next field, the flag that will indicate whether the listing is an offer or request. I choose Enum
, or a list of choices.
? Add another field to the entry? (y/n) › y
✔ Choose field type: · Enum
? Enter the name of the enum: › listing_type
✘ Input must be Pascal case.
My first error — so helpful. I feel like I’m getting a free Rust education! Let’s try again.
✔ Enter the name of the enum: · ListingType
Now it asks me for variant names:
? Enter the name of the next variant: ›
Odd language, since I haven’t created any variants yet, but I can forgive such a helpful little tool. Careful to stick with PascalCase this time, I define the two variants:
✔ Enter the name of the next variant: · Offer
? Add another variant to the enum? (y/n) · y
✔ Enter the name of the next variant: · Request
? Add another variant to the enum? (y/n) · n
Then I name the field (this time I can use snake_case):
✔ Field name: · listing_type
Now it asks me if I want to include the field in the scaffolded UI code. Guess it’s going to generate a basic UI for me; that’s helpful!
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · Select
Now I’ll add two simple string fields — a title and description. You’ll note that there are two types of UI widget to choose from: TextField
and TextArea
. The former is good for a title; the latter is better for an expanded description.
✔ Choose field type: · String
✔ Field name: · title
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextField
✔ Choose field type: · String
✔ Field name: · description
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextArea
Finally, a flag that lets a gardener hide the listing when they’re not currently offering or wanting something but may want to again in the future.
✔ Choose field type: · bool
✔ Field name: · is_active
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · Checkbox
Now I tell the scaffolder that I’m done defining fields. Next it asks me what sorts of ‘CRUD functions’ I want to create. Sort of — more like ‘UD’; create and read are already assumed.
Chosen fields: creator, listing_type, title, description, is_active
? Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? ›
✔ Update
✔ Delete
The next question exposes the finer details of designing for the DHT’s graph database, but it helpfully recommends a choice for me. It’s asking me if the first entry in an update history should point to the newest entry (by default, an updated entry only points to its immediate replacement).
? Should a link from the original entry be created when this entry is updated? ›
❯ Yes (more storage cost but better read performance, recommended)
No (less storage cost but worse read performance)
And it’s done! As before, it finishes off with guidance on what my next step should be:
Entry type "listing" scaffolded!
Add new collections for that entry type with:
hc scaffold collection
I’ve learned that Holochain doesn’t have a built-in idea of a table or collection, but you can create one using links off a well-defined and easy-to-find base entry called an ‘anchor’. Let’s try the command and see what it does.
[holonix:~/Holochain/gardenswap]$ hc scaffold collection
✔ Collection name (snake_case, eg. "all_posts"): ·
I’m intuiting that the suggested name means one can create a collection of all entries of a certain type. So I call it all_listings
.
✔ Collection name (snake_case, eg. "all_posts"): · all_listings
Now it’s asking me about the intended scope of this collection.
? Which type of collection should be scaffolded? ›
❯ Global (get all entries of the selected entry types)
By author (get entries of the selected entry types that a given author has created)
The next question reveals something cool; it’s obviously picking up on code that’s already been generated. It allows me to select from a list (currently one item long) of existing entry types.
? Which entry type should be collected? ›
❯ Listing
Now it generates the collection code, along with some helpful advice for when I start working on the UI:
Collection "all_listings" scaffolded!
At first, the UI for this application is empty. If you want the newly scaffolded collection to be the entry point for its UI, import the element in `ui/src/holochain-app.ts`:
import './gardenswap/gardenswap/all-listings';
And insert it in the `<div id="content"></div>` like this:
<div id="content"><all-listings></all-listings></div>
I recall that there was another option offered to me, to create a by-author collection. That sounds important too; I want people to be able to drill down and only see one person’s listings.
[holonix:~/Holochain/gardenswap]$ hc scaffold collection
✔ Collection name (snake_case, eg. "all_posts"): · listings_by_creator
? Which type of collection should be scaffolded? ›
✔ Which type of collection should be scaffolded? · By author (get entries of the selected entry types that a given author has created)
? Which entry type should be collected? · Listing
Collection "listings_by_creator" scaffolded!
Okay, the next step is getting a bit into advanced territory. Maybe this is unnecessary for an introductory article, but I think it’s interesting to learn how to remodel things I took for granted back in my MySQL days (I told you I was old-fashioned). So we’re going to do an entry type that references another entry type (in SQL terms this would be a ‘foreign key’ relationship). It’ll be a listing ‘format’ — things like “1 pint” for a cherry tomato listing, or “packet of 20” and “bulk pack of 100” for pumpkin seeds.
The first field is an entry hash, which will point to the listing we’re attaching a format to.
[holonix:~/Holochain/gardenswap]$ hc scaffold entry-type listing_format
Which fields should the entry contain?
✔ Choose field type: · EntryHash
Just like when I pointed to an agent ID (which also mimicked a foreign key relationship), it asks me if I want to link back from the referenced entry to this entry. And I do, of course.
✔ Should a link from this field be created when this entry is created? · yes
Then it asks what sort of entry we should point to, giving us a list of already defined entry types. From there it suggests a field name — once again, it’s choosing the most sensible default to save us time.
✔ Which entry type is this field referring to? · Listing
✔ Field name: · listing_hash
Lastly I’ll add a simple text field for the description and an integer field for the current quantity, and finish it off with CRUD functionality.
✔ Choose field type: · String
✔ Field name: · description
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextField
✔ Choose field type: · i32
✔ Field name: · quantity
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · Slider
Chosen fields: listing_hash, description, quantity
✔ Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? · Update, Delete
✔ Should a link from the original entry be created when this entry is updated? · Yes (more storage cost but better read performance, recommended)
Entry type "listing_format" scaffolded!
Add new collections for that entry type with:
hc scaffold collection
You’d think I would create a collection for this, some sort of listing-to-format collection. But in this case, because I already told the scaffolder to link from the listing to the format that references it, the listing entry is the collection anchor for all the connected format entries.
And for my final touch I’ll add a place where gardeners can introduce themselves. It’ll have just a few text fields — name, location, and bio. Of course it also needs to link to an agent ID. This is pretty similar to what we’ve seen already.
[holonix:~/Holochain/gardenswap]$ hc scaffold entry-type profile
Which fields should the entry contain?
✔ Choose field type: · AgentPubKey
✔ Should a link from this field be created when this entry is created? · yes
✔ Which role does this agent play in the relationship ? (eg. "creator", "invitee") · person
✔ Field name: · person
✔ Choose field type: · String
✔ Field name: · name
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextField
✔ Choose field type: · String
✔ Field name: · location
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextField
✔ Choose field type: · String
✔ Field name: · bio
✔ Should this field be visible in the UI? · yes
✔ Choose widget to render this field: · TextArea
Chosen fields: person, name, location, bio
✔ Which CRUD functions should be scaffolded (SPACE to select/unselect, ENTER to continue)? · Update, Delete
✔ Should a link from the original entry be created when this entry is updated? · Yes (more storage cost but better read performance, recommended)
Entry type "profile" scaffolded!
Add new collections for that entry type with:
hc scaffold collection
And that’s it! Now let’s try it out.
Trying my new hApp
[holonix:~/Holochain/gardenswap]$ npm install
... lots of NPM output follows ...
[holonix:~/Holochain/gardenswap]$ npm start
... more NPM output, plus Rust compilation output ...
Wow! It’s… not pretty, but it’s something! On the left screen, in front of a staggering amount of console output, I see two hApp windows running the scaffolded UI. On the right, I see something called the Holochain Playground. It’s like a hApp inspector — it taps into a running conductor, which in this case is hosting the two agents whose UIs appear on the left. As the agents start interacting and contributing data to the DHT, you can see the graph of all the DHT’s data in the upper-right panel. It’s sort of a mix between a diagnostic tool and a learning tool.
The beginnings of a UI
There’s not much to see here, really, so I’m going to edit the HTML templates a bit. Recalling the suggestion the scaffolder made when I scaffolded my first collection, I open up the UI entrypoint file:
[holonix:~/Holochain/gardenswap]$ vim ui/src/holochain-app.ts
(Yes, vim
— I really am old-fashioned.) I add a new line right before the @customElement('holochain-app')
line:
import './gardenswap/gardenswap/all-listings';
And replace everything inside the <div id="content">
element with this:
<all-listings></all-listings>
And magically, the UIs hot-reload just like we Web2 devs expect them to. But there aren’t any listings, and there’s no way to add one, so I’m going to make a couple more modifications. I add one more import after the previous one:
import ‘./gardenswap/gardenswap/create-listing’;
And one more element after </all-listings>
:
<create-listing></create-listing>
Now, when it hot-reloads, I see a form to enter new listing details! However, if I type something in and save it, I get an error. After a bit of sleuthing, I discover it’s because the creator
property isn’t part of the form, so it’s valueless when I try to save it. So I open up the file for the form:
[holonix:~/Holochain/gardenswap]$ vim ui/src/gardenswap/gardenswap/create-listing.ts
And I hard-wire the creator ID to be the agent ID of whoever’s typing in the form. In the createListing
function I change this line:
creator: this.creator,
To this:
creator: this.client.myPubKey,
After it hot-reloads, I try creating a new listing, and this time it works!
That was fast!
So there you have it. In speed-run mode I was able to scaffold the entire hApp and make edits to the UI in 8 minutes and 30 seconds without copy/pasting a single command! Of course it took longer the first time I tried it — there were packages to download, I had to figure out what I wanted my data model to look like, and I was learning a bunch too. And there’s obviously a lot more work to be done on UI, permissioning (right now people can edit and delete each other’s listings!), and putting something useful into the automated tests (you’ll find them in the tests/
folder). But having the skeleton of a fully working, fully peer-to-peer dApp in less than 15 minutes is a pretty great feeling. I’ve got something to play around with, to show others, and to start building on.
Here's the speed run at 2×, if you want to watch what I did (apologies to people reading this on Safari for iOS; I hear it doesn't work there).
In the next article, we'll go through all the other subcommands available in the hc
command.