DEV Community

Cover image for Rust + Web3 For All Your Backend Code
James Bachini
James Bachini

Posted on

1 1

Rust + Web3 For All Your Backend Code

Introduction: Web3 Backends for Modern Developers

This tutorial will show you how to leverage a React frontend with a Rust smart contract for Soroban as a powerful alternative to traditional database backed applications.

  • React is the worlds most popular Javascript framework for frontend development
  • Stellar smart contract platform offers developers a way to build permissionless, decentralized applications with the performance and safety guarantees of Rust.

Why Web3?

Before we dive in, let's talk about why you might want to consider this approach:

  1. Decentralization: Your data lives on the blockchain rather than a centralized server
  2. Immutability: Once written, your data can't be tampered with
  3. Transparency: All operations are public and verifiable
  4. Safety: Rust's memory safety guarantees help prevent common programming errors
  5. Performance: Soroban is designed for scalability and efficiency

Stellar has been around since 2014 and has proven itself as a reliable blockchain network with low transaction costs and fast settlement times.

Prerequisites

  • Basic knowledge of Rust (or willingness to learn!)
  • Familiarity with web development concepts
  • Node, Rust & Git installed

Setting Up Your Environment

Let's start by getting your development environment ready. We'll need the Soroban CLI to interact with the Stellar network and deploy our contracts.

# Install Rust if you haven't already
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add the WebAssembly target
rustup target add wasm32-unknown-unknown

# Install Soroban CLI
cargo install --locked soroban-cli

# Verify installation
soroban --version
Enter fullscreen mode Exit fullscreen mode

Project Structure: Stellar Soroban Boilerplate

We'll be working with a boilerplate that provides a solid foundation for building web3 applications. The repository includes both smart contract code written in Rust and a React frontend interface to interact with it.

All the code for this tutorial is available at: https://github.com/jamesbachini/Stellar-Soroban-Boilerplate

Let's clone the repository:

git clone https://github.com/jamesbachini/Stellar-Soroban-Boilerplate
cd Stellar-Soroban-Boilerplate
Enter fullscreen mode Exit fullscreen mode

The project structure looks like this:

Stellar-Soroban-Boilerplate/
├── soroban/contracts/src
│   └── lib.rs   # A simple key-value store contract
│   └── test.rs  # Unit tests for the contract
├── src/
│   └── App.js   # Frontend example
└── README.md
Enter fullscreen mode Exit fullscreen mode

Understanding the Key-Value Store Contract

At the heart of our Web3 backend is a key-value store contract. This serves as a simple database replacement that stores data on the Stellar blockchain instead of a traditional database server.

Let's look at the key parts of this contract:

#![no_std]
use soroban_sdk::{contract, contractimpl, Env, symbol_short, Symbol};

#[contract]
pub struct StarterContract;

#[contractimpl]
impl StarterContract {
    pub fn set(env: &Env, key: Symbol, value: Symbol) {
        env.storage().persistent().set(&key, &value);
        env.events().publish((symbol_short!("SET"), &key), &value);
    }

    pub fn get(env: &Env, key: Symbol) -> Option<Symbol> {
        env.storage().persistent().get(&key)
    }

    pub fn remove(env: &Env, key: Symbol) {
        env.storage().persistent().remove(&key);
    }
}

mod test;
Enter fullscreen mode Exit fullscreen mode

This contract allows any user to:

  1. Store data with set(key, value)
  2. Retrieve data with get(key)

The set function stores the value on-chain and also fires an event which can be monitored on a frontend for real-time updates.

Building and Deploying Your Contract

Let's build and deploy our key-value store contract to the Soroban network:

cargo build --target wasm32-unknown-unknown --release

stellar keys generate mywallet

stellar keys fund mywallet

stellar contract deploy  --wasm target/wasm32-unknown-unknown/release/StarterContract.wasm --source YourWalletAlias --network testnet
Enter fullscreen mode Exit fullscreen mode

The stellar keys commands generates a new wallet address or keypair and then funds it with testnet XLM to pay the deployment costs. You should see something like this:

Contract deployed successfully!
Contract ID: CABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF12345678
Enter fullscreen mode Exit fullscreen mode

Take note of this Contract ID - You can view it at: https://stellar.expert/explorer/testnet

You'll need it later for the frontend to interact with your contract!

The Web3 Frontend

Now that we have our contract deployed, let's look at how to interact with it from a web application. The boilerplate includes a simple frontend that demonstrates this.

First, let's set up a react dev environment:

npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Now let's edit src/App.js and modify lines 13, 15 & 16 to replace the contract ID and wallet keys which you can get via the stellar keys tool.

Let's examine the set function:

const handleSet = async (e) => {
  e.preventDefault();
  try {
    const inputKey = StellarSdk.nativeToScVal(key, { type: "symbol" });
    const inputValue = StellarSdk.nativeToScVal(value, { type: "symbol" });
    const account = await rpc.getAccount(accountPublicKey);
    const tx = new StellarSdk.TransactionBuilder(account, {
        fee: StellarSdk.BASE_FEE,
        networkPassphrase: StellarSdk.Networks.TESTNET,
      })
      .addOperation(contract.call("set", inputKey, inputValue))
      .setTimeout(30)
      .build();
    const preparedTx = await rpc.prepareTransaction(tx);
    preparedTx.sign(accountKeypair);
    const txResult = await rpc.sendTransaction(preparedTx);
    console.log('txResult', txResult);
    setKey('');
    setValue('');
  } catch (err) {
    setError('Failed to set value: ' + err.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

The handleSet function first prevents the default form submission behavior and converts the key and value from form inputs into a special Contract Value format as symbol types. It then retrieves the user's Stellar account and constructs a new transaction using the Stellar SDK. This transaction includes an operation to call the "set" method on a smart contract, passing in the converted key and value parameters.

After setting a timeout and building the transaction, it's prepared for submission, signed using the user's keypair, and sent to the Stellar network.

Note that when updating on-chain we have to pay a tx fee to modify the state and cover network/storage costs. When reading data there is no cost and we can just simulate the tx as we will see in the next snippet for the get function.

Simulated Transaction Slide

const handleGet = async (e) => {
  e.preventDefault();
  try {
    const inputGetKey = StellarSdk.nativeToScVal(getKey, { type: "symbol" });
    const account = await rpc.getAccount(accountPublicKey);
    const tx = new StellarSdk.TransactionBuilder(account, {
        fee: StellarSdk.BASE_FEE,
        networkPassphrase: StellarSdk.Networks.TESTNET,
      })
      .addOperation(contract.call("get", inputGetKey))
      .setTimeout(30)
      .build();
    rpc.simulateTransaction(tx).then((sim) => {
      const decoded = StellarSdk.scValToNative(sim.result?.retval);
      setGetValue(decoded);
    });
  } catch (err) {
    setError('Failed to get value: ' + err.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

In this function we are using the rpc.simulateTransaction function to ask our RPC node which is our entry point to the network to return the data from it's copy of the blockchain state.

This data is decoded and returned to be displayed on the frontend.

Next Steps

While our example uses a simple key-value store, you can build much more sophisticated data models. Here is an example of a function that has access control meaning only a certain account can update the ledger.

In this example the admin: Address variable would be set in the constructor or somewhere else and then we check that they are the ones that are calling the set function so that only they can update data.

#[contractimpl]
pub fn set(env: &Env, key: Symbol, value: Symbol) {
    let store = env.storage().persistent();
    let admin: Address = store.get(&symbol_short!("admin")).unwrap();
    admin.require_auth();
    store.set(&key, &value);
    env.events().publish((symbol_short!("SET"), &key), &value);
}
Enter fullscreen mode Exit fullscreen mode

Handling Data Limitations

Blockchains in their current form are good for storing small amounts of critical data. Scaling efforts are constantly pushing the boundaries of what we can do within a smart contract.

Here are some strategies for managing larger datasets:

  1. Store Hashes: For large files or datasets, store only a hash or reference on-chain, with the full data in IPFS or a centralized service
  2. Paginate Data: If you have collections, implement pagination to retrieve data in chunks. Avoid iterating over an array of unknown size i.e. user data
  3. Use Events: For historical data, use blockchain events rather than storing everything. These can be indexed and referred back to as required.

For web developers looking to step into Web3, Soroban provides a gentle learning curve with familiar concepts and excellent documentation. The Stellar network efficiency makes it practical for real-world applications beyond simple experiments.

Additional Resources

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.

Tiger Data image

🐯 🚀 Timescale is now TigerData: Building the Modern PostgreSQL for the Analytical and Agentic Era

We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future.

So we’re changing our name: from Timescale to TigerData. Not to change who we are, but to reflect who we’ve become. TigerData is bold, fast, and built to power the next era of software.

Read more

👋 Kindness is contagious

Explore this insightful write-up, celebrated by our thriving DEV Community. Developers everywhere are invited to contribute and elevate our shared expertise.

A simple "thank you" can brighten someone’s day—leave your appreciation in the comments!

On DEV, knowledge-sharing fuels our progress and strengthens our community ties. Found this useful? A quick thank you to the author makes all the difference.

Okay