Skip to main content
Version: 1.x

Add providers

Going from "Hello World" to a full-fledged application is simply a matter of identifying the capabilities your application needs, and then adding providers to deliver those capabilities. Providers in wasmCloud can invoke a function handler in your component in response to some external trigger (as with the first-party httpserver and messaging providers) and components can invoke providers in order to handle a request (as with our keyvalue providers). Our "Hello World" already uses the httpserver provider, so let's add more features to this application with functional code and more providers.

Adding functionality

Rust & WebAssembly

To perform the steps in this guide, you'll need to have a Rust toolchain installed locally and the wasm32 target installed:

bash
rustup target add wasm32-wasi

Let's extend this application to do more than just say "Hello from Rust!" Using the path_with_query method on the incoming request, we can check the request for a name provided in a query string, and then return a greeting with that name. If there isn't one or the path isn't in the format we expect, we'll default to saying "Hello, World!".

rust
wit_bindgen::generate!({
  generate_all
});

use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;

struct HttpServer;

impl Guest for HttpServer {
  fn handle(_request: IncomingRequest, response_out: ResponseOutparam) { 
  fn handle(request: IncomingRequest, response_out: ResponseOutparam) { 
      let response = OutgoingResponse::new(Fields::new());
      response.set_status_code(200).unwrap();
      let response_body = response.body().unwrap();
      let name = match request 
          .path_with_query()
          .unwrap()
          .split("=")
          .collect::<Vec<&str>>()[..]
      {
          // query string is "/?name=<name>" e.g. localhost:8080?name=Bob
          ["/?name", name] => name.to_string(),
          // query string is anything else or empty e.g. localhost:8080
          _ => "World".to_string(),
      };
      response_body
          .write()
          .unwrap()
          .blocking_write_and_flush(b"Hello from Rust!\n") 
          .blocking_write_and_flush(format!("Hello, {}!\n", name).as_bytes()) 
          .unwrap();
      OutgoingBody::finish(response_body, None).expect("failed to finish response body");
      ResponseOutparam::set(response_out, Ok(response));
  }
}

export!(HttpServer);

After changing the code, you can use wash to build the local component:

bash
wash build

Deploying your component

Now that you've made an update to your component, you can use wash to stop the previous version. You can stop the component based on its unique identifier, which Wadm automatically assigned based on the name of your application and the name of your component. Once stopped, Wadm will take care of starting the new local copy from the updated file.

bash
wash stop component rust_hello_world-http_component
info

Whenever you make a change to your component that you want to deploy, be sure to run wash build to recompile and generate a new .wasm file.

bash
> curl localhost:8080
Hello, World!
> curl 'localhost:8080?name=Bob'
Hello, Bob!

Adding persistent storage

To further enhance our application, let's add persistent storage to keep a record of each person that this application greeted. We'll use the key-value store provider for this, and just like HTTP server, you won't need to pick a library or a specific vendor implementation yet. You'll just need to add the appropriate interface to your component, and then you can pick a provider at runtime.

In your template, we already included the wasi:keyvalue interface for interacting with a key value store. We can also use the wasi:logging interface to log the name of each person we greet. Before you can use the functionality of those interfaces, you'll need to add a few imports to your wit/world.wit file:

wit
package wasmcloud:hello;

world hello {
  import wasi:keyvalue/atomics@0.2.0-draft; 
  import wasi:keyvalue/store@0.2.0-draft; 
  import wasi:logging/logging; 

  export wasi:http/incoming-handler@0.2.0;
}

Let's use the atomic increment function to keep track of how many times we've greeted each person.

rust
    let name = match request
        .path_with_query()
        .unwrap()
        .split("=")
        .collect::<Vec<&str>>()[..]
    {
        // query string is "/?name=<name>" e.g. localhost:8080?name=Bob
        ["/?name", name] => name.to_string(),
        // query string is anything else or empty e.g. localhost:8080
        _ => "World".to_string(),
    };

    wasi::logging::logging::log( 
        wasi::logging::logging::Level::Info,
        "",
        &format!("Greeting {name}"),
    );

    let bucket =
        wasi::keyvalue::store::open("").expect("failed to open empty bucket");
    let count = wasi::keyvalue::atomics::increment(&bucket, &name, 1)
        .expect("failed to increment count");

    response_body
        .write()
        .unwrap()
        .blocking_write_and_flush(format!("Hello x{count}, {name}!\n").as_bytes())
        .unwrap();

We've made changes, so run wash build again to compile the Wasm component.

bash
wash build

Deploying a key-value store provider

Our component is prepared to use a key-value store, and now that we've built it we're ready to choose an implementation. A great option for local development and testing is the Redis provider, and will only require you to have redis-server or Docker installed.

Install and launch the local redis server in the background

bash
redis-server &

The Redis provider will mediate a connection to the Redis server, which is running external to wasmCloud. We can modify our wadm.yaml to include the Redis provider and configure a link for our component. Since we're nearing the end of this tutorial, we'll provide the full manifest here:

yaml
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: hello-world
  annotations:
    description: 'HTTP hello world demo, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
spec:
  components:
    - name: http-component
      type: component
      properties:
        # Your manifest will point to the path of the built component, you can also
        # start published components from OCI registries
        image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
      traits:
        - type: spreadscaler
          properties:
            replicas: 1
        # The new key-value link configuration
        - type: link
          properties:
            target: kvredis
            namespace: wasi
            package: keyvalue
            interfaces: [atomics, store]
            target_config:
              - name: redis-url
                properties:
                  url: redis://127.0.0.1:6379
    # The new capability provider
    - name: kvredis
      type: capability
      properties:
        image: ghcr.io/wasmcloud/keyvalue-redis:0.27.0
    - name: httpserver
      type: capability
      properties:
        image: ghcr.io/wasmcloud/http-server:0.22.0
      traits:
        - type: link
          properties:
            target: http-component
            namespace: wasi
            package: http
            interfaces: [incoming-handler]
            source_config:
              - name: default-http
                properties:
                  address: 127.0.0.1:8080
What are those images in the deployment manifest?

You'll notice that the kvredis and httpserver providers are pulled from images hosted on GitHub Packages. The wasmCloud ecosystem uses the OCI image specification to package components and providers—these component and provider images are not container images, but conform to OCI standards and may be stored on any OCI-compatible registry.

Deploy the latest version of our application including the key-value provider. Then, again, we can test our new functionality.

bash
> wash app deploy wadm.yaml
> curl 'localhost:8080?name=Bob'
Hello x1, Bob!
> curl 'localhost:8080?name=Bob'
Hello x2, Bob!
> curl 'localhost:8080?name=Alice'
Hello x1, Alice!

Moving on

In this tutorial, you added a few more features and persistent storage to a simple microservice. You also got to see the process of developing with interfaces, where the code you write is purely functional, doesn't require you to pick a library or vendor upfront, and allows you to change your application separately from its non-functional requirements. You can continue to build on this application by adding more features, or you can explore additional first-party providers in the wasmCloud repo (see any provider-* directory) to get an idea of what is possible with wasmCloud.

The next page demonstrates scaling via Wadm manifest and gives a high level overview of why this application that you've built already eliminates complexity and pain that developers often face when building applications for the cloud.