Introduction

User management is a fundamental feature for many decentralized applications (hApps) built on Holochain. In this tutorial, we will walk through the process of creating a basic user management zome that handles user profiles in a decentralized way.

Understanding the User Zome

In Holochain, a zome is a modular component that encapsulates specific functionality. The User Zome is designed to manage user profiles within a Holochain app. It consists of two main parts:

  1. Integrity Zome: This zome defines data structures and validation rules, ensuring that all data entries conform to the schema and pass validation checks before being committed to the DHT (Distributed Hash Table). For the User Zome, it defines the User entry type and its validation logic, ensuring consistent and reliable user profiles.

  2. Coordinator Zome: This zome handles business logic and external interactions. It provides functions that other parts of the application or external agents can call to interact with the data. In the User Zome, it implements functions for creating, updating, and retrieving user profiles.

Technical Implementation of the User Zome

Entry Types

The core of our User Zome is the User entry type. Here’s how we define it:

#[hdk_entry_helper]
pub struct User {
    /// The full name of the user
    pub name: String,
    /// Display name for the user
    pub nickname: String,
    /// User's biographical information
    pub bio: String,
    /// Optional profile picture (serialized)
    pub picture: Option<SerializedBytes>,
    /// User's skills
    pub skills: Vec<String>,
    /// Contact information
    pub email: String,
    pub phone: Option<String>,
    /// Location details
    pub time_zone: String,
    pub location: String,
}

Each field in the User struct serves a specific purpose:

  • name and nickname: For user identification
  • bio: For user description
  • picture: An optional profile picture
  • skills: A list of user skills
  • email and phone: Contact information
  • time_zone and location: Geographic information

Validation Rules

In the Integrity Zome, we implement validation rules to ensure data consistency. Here’s an example of validation for the User entry:

pub fn validate_create_user(
    _action: EntryCreationAction,
    user: User
) -> ExternResult<ValidateCallbackResult> {
    // Validate email format
    if !is_valid_email(&user.email) {
        return Ok(ValidateCallbackResult::Invalid("Invalid email format".into()));
    }
 
    // Validate required fields
    if user.name.is_empty() || user.nickname.is_empty() {
        return Ok(ValidateCallbackResult::Invalid("Name and nickname are required".into()));
    }
 
    Ok(ValidateCallbackResult::Valid)
}

Business Logic

The Coordinator Zome implements the core functions for managing user data. Here are the essential functions:

#[hdk_extern]
pub fn create_user(user: User) -> ExternResult<ActionHash> {
    create_entry(&user)
}
 
#[hdk_extern]
pub fn get_user(action_hash: ActionHash) -> ExternResult<Option<User>> {
    get(action_hash, GetOptions::default())
}
 
#[hdk_extern]
pub fn update_user(input: UpdateUserInput) -> ExternResult<ActionHash> {
    update_entry(input.original_action_hash, &input.updated_user)
}
 
#[hdk_extern]
pub fn get_all_users() -> ExternResult<Vec<Record>> {
    let path = Path::from("all_users");
    let links = get_links(path.path_entry_hash()?, LinkTypes::AllUsers, None)?;
    
    let mut users = Vec::new();
    for link in links {
        if let Some(record) = get(link.target.clone(), GetOptions::default())? {
            users.push(record);
        }
    }
    
    Ok(users)
}

Step-by-Step Guide to Building the User Zome

  1. Set Up the Project Structure

    nix develop
    hc scaffold zome users
  2. Define the Entry Types

    • Create the User struct in integrity.rs
    • Add necessary derive macros and field definitions
  3. Implement Validation Rules

    • Add validation functions in the integrity zome
    • Define rules for email format, required fields, etc.
  4. Create Business Logic

    • Implement CRUD operations in the coordinator zome
    • Add functions for creating, reading, updating users
    • Set up links for user discovery

Integrating the User Zome with the UI

Here’s an example of how to create a user profile component using SvelteKit and Svelte 5 features:

<script lang="ts">
  import { createUser } from '$lib/api';
  
  let user = $state({
    name: '',
    nickname: '',
    bio: '',
    email: '',
    skills: [],
    time_zone: '',
    location: ''
  });
 
  $derived.by(() => {
    // Validate form
    const isValid = user.name && user.email;
    return isValid;
  });
 
  async function handleSubmit() {
    try {
      const result = await createUser(user);
      // Handle success
    } catch (error) {
      // Handle error
    }
  }
</script>
 
<form onsubmit={handleSubmit}>
  <input type="text" bind:value={user.name} placeholder="Full Name" required />
  <input type="text" bind:value={user.nickname} placeholder="Nickname" required />
  <textarea bind:value={user.bio} placeholder="Bio"></textarea>
  <input type="email" bind:value={user.email} placeholder="Email" required />
  <!-- Add other fields -->
  <button type="submit">Create Profile</button>
</form>

Testing and Deployment

To test the User Zome:

nix develop --command "cargo test"

This will run the test suite, which should include:

  • Validation tests for user creation
  • CRUD operation tests
  • Link tests for user discovery

Conclusion

We’ve built a basic User Management zome that provides essential functionality for managing user profiles in a Holochain app. This foundation can be extended with additional features such as user search, profile updates, and more complex user relationships depending on your application’s needs.