CRUD with WASI Key-Value
Build and deploy a CRUDdy Wasm component
This tutorial will show you how to create and deploy a distributed WebAssembly application with wasmCloud, using Python, Rust, Go, or TypeScript. Building on what you've learned in the Quickstart, we'll explore the fundamentals of wasmCloud development by making a simple guestbook that performs CRUD operations using several capabilities—abstracted requirements like HTTP handling or key-value storage, fulfilled by WebAssembly components.
By the end, you'll understand how you can use wasmCloud to orchestrate components and build a distributed application. Along the way, you'll learn how to create a component from scratch and how to use WebAssembly Standard Interface (WASI) APIs.
Let's get going!
A component is portable Wasm code that is interoperable with any other component, regardless of the language in which each component was written. The interoperability of WebAssembly components drives wasmCloud's "building blocks" approach—you can write original code in any language that compiles to WebAssembly and then plug in the capabilities you need (e.g. HTTP) without worrying about how that functionality is implemented. Learn more about WebAssembly components in the Concepts section.
Before you get started
In this tutorial, we'll be using two core tools at the heart of wasmCloud: the wasmCloud Shell (wash) and wasmCloud Application Deployment Manager (wadm).
washprovides a command-line interface for wasmCloud, helping you run hosts, build and deploy components, and manage your installation.wadmorchestrates wasmCloud workloads according to declarative manifests.
If you haven't installed wasmCloud already, head on over to the installation instructions and then return here.
This tutorial uses wasmCloud v0.82 and first-party providers designed for WASI HTTP 0.2.0 and WASI Key-Value Store 0.1.0. You shouldn't have to worry about WASI API versions, since the API definitions come packaged with the wasmCloud templates used here.
- Rust
 - TinyGo
 - TypeScript
 - Python
 
You will also need both the Rust toolchain and the wasm32-wasi target installed on the same machine. wash depends on the Rust toolchain to compile Rust code to Wasm.
rustup target add wasm32-wasi
Creating a new component
In wasmCloud, an application component is a WebAssembly component dedicated to an application's creative logic. Typically, we will simply refer to this as a "component." Historically, wasmCloud referred to this piece of an application as an "actor," but that term is deprecated in favor of "component" and CLI commands will change as of v1.0.
With wash installed, you can run the following command in your shell to create a new component from a simple template:
wash new actor cruddy-rust --git wasmcloud/wasmcloud --subfolder examples/rust/actors/http-hello-world --branch 0.82-examples
This command instructs wash to create a new component called "cruddy-rust". For our template, we're using the http-hello-world template from the 0.82 branch of the wasmCloud project repo on GitHub. The new actor command always creates new components from templates—if you don't specify one from the outset, wash will provide you with a set of options.
The repo we're using gives us the skeleton for a new component in Rust. Let's take a look at the cruddy-rust directory:
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│   └── lib.rs
├── wadm.yaml
├── wasmcloud.toml
└── wit
Here we have the standard Rust project files as well as three pieces that make up a wasmCloud application component:
wadm.yamlis a declarative deployment manifest used bywadm.wasmcloud.tomlis a metadata and permissions configuration file for the application component.- The 
witdirectory holds WebAssembly Interface Type (WIT) definitions for standard APIs used across the Wasm ecosystem—we'll discuss these at greater length in a moment. 
Let's open wasmcloud.toml and have a look around:
name = "http-hello-world"
language = "rust"
type = "actor"
version = "0.1.0"
[actor]
wit_world = "hello"
wasm_target = "wasm32-wasi-preview2"
Here we have metadata for details like naming and versioning. For now, we're most interested in the [actor] fields:
wit_worldpoints the application to a set of interfaces (known as a "world") defined in thewitdirectory.wasm_targetspecifies the Wasm compilation target.
Now let's take a look at the wit directory.
Understanding WIT dependencies
We will interact with the httpserver and keyvalue capabilities via WebAssembly System Interface (WASI) APIs—ecosystem-standard APIs used for communication with and between components.
WASI is especially useful for building and maintaining standard and popular libraries in an efficient and language-agnostic way—so developers can focus on writing simple, portable, idiomatic code.
Take a look at the contents of wit:
├── deps
├── deps.toml
└── world.wit
depsis a directory that holds WIT definitions.deps.tomlis a TOML configuration file used to manage WIT dependencies.world.witdefines the interfaces that comprise your WIT "world."
Our template has pre-populated the wit directory with relevant definitions. However, if you wish to select your WIT dependencies and populate the directory yourself, you can use the wit-deps tool.
Open deps.toml. Here we have specified interfaces that we wish to utilize in our app. WASI interfaces like wasi-http or wasi-keyvalue are available to browse in the WebAssembly project's GitHub repo.
WIT definitions contain documentation as comments within the WIT files themselves. It is very useful to read through the WIT comprehensively before using a new interface.
Take a look at /deps/http/types.wit. Here you'll find a detailed explanation of the interface's types. Note the outgoing-request type—it will come up again in a moment.
/// Represents an outgoing HTTP Request.
resource outgoing-request
With our WIT dependencies in place, we can specify interface imports and exports in world.wit. A WIT world is a set of standard interfaces that you can use to interact with components via defined functions and types. The WIT interfaces don't provide functionality in themselves—instead, they provide a common language for the contracts between components.
Remember that we targeted the hello world in our wasmcloud.toml file; this is where that world is defined. Let's update the hello world in world.wit:
package wasmcloud:hello;
world hello {
  import wasi:keyvalue/eventual@0.1.0; 
  export wasi:http/incoming-handler@0.2.0;
}
The wasmcloud.toml file points to the hello world in world.wit, and the hello world connects to our WIT definitions.
Now that our dependencies are defined, we're ready to get coding.
Working with WASI APIs
Open src/lib.rs.
wit_bindgen::generate!({
    world: "hello",
    exports: {
        "wasi:http/incoming-handler": HttpServer,
    },
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;
struct HttpServer;
impl Guest for HttpServer {
    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();
        response_body
            .write()
            .unwrap()
            .blocking_write_and_flush(b"Hello from Rust!\n")
            .unwrap();
        OutgoingBody::finish(response_body, None).expect("failed to finish response body");
        ResponseOutparam::set(response_out, Ok(response));
    }
}
Most of this file is simple boilerplate. We've made WASI APIs available, but how do we use those APIs in Rust?
The answer is in the first lines. When we build the application with the wash build command, the builder will use these instructions to run the built-in wit-bindgen tool and generate bindings between the functions we saw in the WIT dependencies and Rust.
Let's try it out. In the root of the project directory:
wash build
This will create a build directory with a compiled .wasm artifact for the app. It will also create a target directory including dependencies such as the generated bindings.
In Rust, using WASI APIs is fairly intuitive, especially with IDE suggestions and autofills. If we've read through the keyvalue WIT, we know that we will need an instance of the OutgoingValue type later. We can create it this way:
// In the language of WASI, here we're creating a new outgoing-value resource
let value: OutgoingValue =
  wasi::keyvalue::types::OutgoingValue::new_outgoing_value();
At each stage from wasi:: to keyvalue:: to types:: and beyond, our IDE can provide us with the available options as documented in the WIT. Taken in conjunction, our WIT files and code completion gives us the tools we need to use the API and put the pieces together. For now, put this at the top of our handler function.
So what else do we have in lib.rs?
Most of the code is in a handle function responding to requests:
impl Guest for HttpServer {
    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();
        response_body
            .write()
            .unwrap()
            .blocking_write_and_flush(b"Hello from Rust!\n")
            .unwrap();
        OutgoingBody::finish(response_body, None).expect("failed to finish response body");
        ResponseOutparam::set(response_out, Ok(response));
    }
}
Now, at the top of the handle function, we'll use the WASI HTTP API's path_with_query method on our incoming request—much like in the Quickstart. This will extract and return a name provided in incoming HTTP queries. (While we're at it, we'll delete the response body, as we'll be replacing that soon.)
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
            _ => "Anonymous".to_string(),
        };
        response_body 
            .write() 
            .unwrap() 
            .blocking_write_and_flush(b"Hello from Rust!\n") 
            .unwrap(); 
        OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 
        ResponseOutparam::set(response_out, Ok(response)); 
This is the first time we're actually implementing a new WASI method in this component, so we should pause to consider exactly what we're doing:
- When we look at our WIT bindings, we can see that 
IncomingRequesthas a methodpath_with_query(). The original WIT tell us thatpath_with_query()returns a string of the request path, including any query. However, the string is contained within a return object and needs to be "unwrapped." - To get at the actual value, we use the 
unwrap()method. - To isolate the name, we split the value at the 
=and collect it as a<Vec<&str>>that we can use elsewhere. 
In order to guide our CRUD operations for the guestbook, we'd also like to know the method of the incoming request. We can detect that with the API as well. Try finding the method of the request with what you've learned so far.
If you guessed that you could use a method on request, well done! Below the previous line add:
// Detect method
let method = request.method(); 
This will return a method of the Method type, which we will be able to use directly.
We've implemented our basic HTTP operations. Now let's turn to key-value storage.
Adding CRUD
We can follow the same procedure we used for wasi-http to explore the wasi-keyvalue API and start adding CRUD operations.
In /wit/deps/keyvalue/types.wit, we see that collections of key-value pairs are referenced as buckets in the API:
/// A bucket is a collection of key-value pairs. Each key-value pair is stored
/// as a entry in the bucket, and the bucket itself acts as a collection of all
/// these entries.
///
/// It is worth noting that the exact terminology for bucket in key-value stores
/// can very depending on the specific implementation. For example,
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
/// 3. Cassandra calls a collection of key-value pairs a column family
/// 4. MongoDB calls a collection of key-value pairs a collection
/// 5. Riak calls a collection of key-value pairs a bucket
/// 6. Memcached calls a collection of key-value pairs a slab
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
///
/// In this interface, we use the term `bucket` to refer to a collection of key-value
We'd like to open a bucket to store the names of our guests, so we'll add the following to our handler function between the method and value assignments:
// Open keyvaluestore bucket
// In the language of WASI, here we're creating a new bucket resource
let bucket =
    wasi::keyvalue::types::Bucket::open_bucket("").expect("failed to open empty bucket"); 
Next we'll create a variable called value of the OutgoingValue type that will hold the new values assigned to keys in PUT/set operations. This is a wasi-keyvalue pattern that we would use in any language: the new_outgoing_value() method is creating a new resource, which we can use to perform synchronous write operations.
// Create a variable for outgoing values - Put/sets will use this
// In the language of WASI, here we're creating a new outgoing-value resource
let value: OutgoingValue =
    wasi::keyvalue::types::OutgoingValue::new_outgoing_value(); 
Now we'll add a switch case that creates/updates, reads, or destroys guestbook records based on the HTTP method of the incoming request. For this rudimentary app, each case will send an appropriate HTTP response:
match method { 
    wasi::http::types::Method::Get => { 
        // If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
        let none = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().is_none(); 
        if none { 
            // Send error message if the value is none
            response_body 
                .write() 
                .unwrap() 
                .blocking_write_and_flush(format!("{name} not found").as_bytes()) 
                .unwrap(); 
            OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 
            ResponseOutparam::set(response_out, Ok(response)); 
        } else { 
            let result = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().unwrap(); 
            // Take this result (of the IncomingValue type) and consume that value to get a uint8 byte array, which we will then send as part of http response
            let in_val = wasi::keyvalue::types::IncomingValue::incoming_value_consume_sync(result).unwrap(); 
            // Send HTTP response
            response_body 
                .write() 
                .unwrap() 
                .blocking_write_and_flush(&in_val).unwrap(); 
            OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 
            ResponseOutparam::set(response_out, Ok(response)); 
        }; 
    }, 
    wasi::http::types::Method::Put => { 
        // Assign value as byte array
        let writval = format!("attending").as_bytes().to_vec(); 
        // Use outgoingvaluewritebodysync to actually write the value
        let _ = value.outgoing_value_write_body_sync(&writval); 
        wasi::keyvalue::eventual::set(&bucket, &name, &value) 
                .expect("failed to set"); 
        // Send HTTP response
        response_body 
            .write() 
            .unwrap() 
            .blocking_write_and_flush(format!("Added {name}").as_bytes()) 
            .unwrap(); 
        OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 
        ResponseOutparam::set(response_out, Ok(response)); 
    }, 
    wasi::http::types::Method::Delete => { 
        // If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
        let none = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().is_none();
        if none {
            // Send error message if the value is none
            response_body
                .write()
                .unwrap()
                .blocking_write_and_flush(format!("{name} not found").as_bytes())
                .unwrap();
            OutgoingBody::finish(response_body, None).expect("failed to finish response body");
            ResponseOutparam::set(response_out, Ok(response));
        } else {
            // Delete key
            let _ = wasi::keyvalue::eventual::delete(&bucket, &name);
            // Send HTTP response
            response_body
                .write()
                .unwrap()
                .blocking_write_and_flush(format!("It's like {name} was never there").as_bytes())
                .unwrap();
            OutgoingBody::finish(response_body, None).expect("failed to finish response body");
            ResponseOutparam::set(response_out, Ok(response));
        };
    } 
    _ => { 
        // Handle other cases
        // Send HTTP response
        response_body 
            .write() 
            .unwrap() 
            .blocking_write_and_flush(format!("Try sending a GET, PUT, or DELETE").as_bytes()) 
            .unwrap(); 
        OutgoingBody::finish(response_body, None).expect("failed to finish response body"); 
        ResponseOutparam::set(response_out, Ok(response)); 
    } 
}; 
Most of our application logic is happening in this block, so let's pause to consider a few important elements.
The condition for each case is the request method returned to the method variable. Within the conditionals, each CRUD operation runs against bucket and name. In the case of wasi::keyvalue::eventual::delete, that's extremely straightforward, since no data is being passed. The set and get operations are just a touch more complicated.
For the set, we use the outgoing_value_write_body_sync method to write the body of the value (defined as a Vec of bytes in writval) to value. With our value written to value, we can pass it into wasi::keyvalue::eventual::set.
The get logic is similar but runs in reverse. wasi::keyvalue::eventual::get gives us a result that must be unwrapped and then consumed with wasi::keyvalue::types::IncomingValue::incoming_value_consume_sync, which gives us a byte array. Byte arrays are a common unit of data in WASI interfaces—note how we use them in HTTP responses as well. Since we have a byte array from our incoming value now, we can pass it directly to the HTTP response.
At this point, lib.rs should look like this:
wit_bindgen::generate!({
    world: "hello",
    exports: {
        "wasi:http/incoming-handler": HttpServer,
    },
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::{http::types::*, keyvalue::types::OutgoingValue};
struct HttpServer;
impl Guest for HttpServer {
    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();
        // Get any name appended to the path via query
        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
            _ => "Anonymous".to_string(),
        };
        // Detect method
        let method = request.method();
    	// Open keyvaluestore bucket
	    // In the language of WASI, here we're creating a new bucket resource
        let bucket =
            wasi::keyvalue::types::Bucket::open_bucket("").expect("failed to open empty bucket");
        // Create a variable for outgoing values - Put/sets will use this
	    // In the language of WASI, here we're creating a new outgoing-value resource
        let value: OutgoingValue =
            wasi::keyvalue::types::OutgoingValue::new_outgoing_value();
        // Switch case on method
        match method {
            wasi::http::types::Method::Get => {
                // If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
                let none = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().is_none();
                if none {
                    // Send error message if the value is none
                    response_body
                        .write()
                        .unwrap()
                        .blocking_write_and_flush(format!("{name} not found").as_bytes())
                        .unwrap();
                    OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                    ResponseOutparam::set(response_out, Ok(response));
                } else {
                    let result = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().unwrap();
                    // Take this result (of the IncomingValue type) and consume that value to get a uint8 byte array, which we will then send as part of http response
                    let in_val = wasi::keyvalue::types::IncomingValue::incoming_value_consume_sync(result).unwrap();
                    // Send HTTP response
                    response_body
                        .write()
                        .unwrap()
                        .blocking_write_and_flush(&in_val).unwrap();
                    OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                    ResponseOutparam::set(response_out, Ok(response));
                };
            },
            wasi::http::types::Method::Put => {
                // Assign value as byte array
                let writval = format!("attending").as_bytes().to_vec();
                // Use outgoingvaluewritebodysync to actually write the value
                let _ = value.outgoing_value_write_body_sync(&writval);
                wasi::keyvalue::eventual::set(&bucket, &name, &value)
                        .expect("failed to set");
                // Send HTTP response
                response_body
                    .write()
                    .unwrap()
                    .blocking_write_and_flush(format!("Added {name}").as_bytes())
                    .unwrap();
                OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                ResponseOutparam::set(response_out, Ok(response));
            },
            wasi::http::types::Method::Delete => {
                // If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
                let none = wasi::keyvalue::eventual::get(&bucket, &name).unwrap().is_none();
                if none {
                    // Send error message if the value is none
                    response_body
                        .write()
                        .unwrap()
                        .blocking_write_and_flush(format!("{name} not found").as_bytes())
                        .unwrap();
                    OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                    ResponseOutparam::set(response_out, Ok(response));
                } else {
                    // Delete key
                    let _ = wasi::keyvalue::eventual::delete(&bucket, &name);
                    // Send HTTP response
                    response_body
                        .write()
                        .unwrap()
                        .blocking_write_and_flush(format!("It's like {name} was never there").as_bytes())
                        .unwrap();
                    OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                    ResponseOutparam::set(response_out, Ok(response));
                };
            }
            _ => {
                // Handle other cases
                // Send HTTP response
                response_body
                    .write()
                    .unwrap()
                    .blocking_write_and_flush(format!("Try sending a GET, PUT, or DELETE").as_bytes())
                    .unwrap();
                OutgoingBody::finish(response_body, None).expect("failed to finish response body");
                ResponseOutparam::set(response_out, Ok(response));
            }
        };
    }
}
Since we've made changes to lib.rs, we'll build again to update the .wasm artifact:
wash build
Start Redis
Up front, we said that we don't have to worry about the underlying software that provides a capability until we deploy. Now it's time to start thinking about which capability providers we want to use. You can explore the list of available capability providers in the wasmCloud GitHub repo. For key-value storage, we'll use Redis.
Start a Redis server with either redis-server or Docker:
redis-server &
docker run -d --name redis -p 6379:6379 redis
Prepare for deployment
The last step to deploy our component is preparing the declarative manifest in wadm.yaml, which defines the desired state for our application when it is running on wasmCloud. This will look familiar if you've used container orchestrators like Kubernetes. The manifest included with the hello-world-rust template looks like this:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: rust-http-hello-world
  annotations:
    version: v0.0.1
    description: 'HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
    experimental: 'true'
spec:
  components:
    - name: http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the actor
        - type: spreadscaler
          properties:
            replicas: 1
        # Link the HTTP server, and inform it to listen on port 8080
        # on the local machine
        - type: linkdef
          properties:
            target: httpserver
            values:
              ADDRESS: 127.0.0.1:8080
    # Add a capability provider that mediates HTTP access
    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
The metadata fields provide naming and versioning for our application. We'll update the name to describe what we've built:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: rust-http-hello-world
  name: cruddy
  annotations:
    version: v0.0.1
    description: "HTTP hello world demo in Rust, using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)"
	description: "CRUD demo"
    experimental: "true"
You will also need both Go and TinyGo installed on the same machine. wash depends on the TinyGo toolchain to compile Go code to Wasm.
Creating a new component
In wasmCloud, an application component is a WebAssembly component dedicated to an application's creative logic. Typically, we will simply refer to this as a "component." Historically, wasmCloud referred to this piece of an application as an "actor," but that term is deprecated in favor of "component" and CLI commands will change as of v1.0.
With wash installed, you can run the following command in your shell to create a new component from a simple template:
wash new actor cruddy --git wasmcloud/wasmcloud --subfolder examples/golang/actors/http-hello-world --branch 0.82-examples
This command instructs wash to create a new component called "cruddy". For our template, we're using the http-hello-world template from the 0.82 branch of the wasmCloud project repo on GitHub. The new actor command always creates new components from templates—if you don't specify one from the outset, wash will provide you with a set of options.
The repo we're using gives us the skeleton for a new actor in Go. Let's take a look at the cruddy directory:
├── go.mod
├── hello.go
├── wadm.yaml
├── wasmcloud.toml
└── wit
Here we have the standard go.mod file for a Go project and a .go file for the project. We also have three pieces that make up a wasmCloud actor:
wadm.yamlis a declarative deployment manifest used bywadm.wasmcloud.tomlis a metadata and permissions configuration file for the actor.- The 
witdirectory holds WebAssembly Interface Type (WIT) definitions for standard APIs used across the Wasm ecosystem—we'll discuss these at greater length in a moment. 
Let's open wasmcloud.toml and have a look around:
name = "http-hello-world"
language = "tinygo"
type = "actor"
version = "0.1.0"
[actor]
wit_world = "hello"
wasm_target = "wasm32-wasi-preview2"
destination = "build/http_hello_world_s.wasm"
Here we have metadata for details like naming and versioning. For now, we're most interested in the three [actor] fields:
wit_worldpoints the actor to a set of interfaces (known as a "world") defined in thewitdirectory.wasm_targetspecifies the Wasm compilation target.destinationdefines the name and location of the final, signed Wasm artifact for the component.
Now let's take a look at the wit directory.
Understanding WIT dependencies
We will interact with the httpserver and keyvalue capabilities via WebAssembly System Interface (WASI) APIs—ecosystem-standard APIs used for communication with and between components.
WASI is especially useful for building and maintaining standard and popular libraries in an efficient and language-agnostic way—so developers can focus on writing simple, portable, idiomatic code.
Take a look at the contents of wit:
├── deps
├── deps.toml
└── world.wit
depsis a directory that holds WIT definitions.deps.tomlis a TOML configuration file used to manage WIT dependencies.world.witdefines the interfaces that comprise your WIT "world."
Our template has pre-populated the wit directory with relevant definitions. However, if you wish to select your WIT dependencies and populate the directory yourself, you can use the wit-deps tool.
Open deps.toml. Here we have specified interfaces that we wish to utilize in our app. WASI interfaces like wasi-http or wasi-keyvalue are available to browse in the WebAssembly project's GitHub repo.
WIT definitions contain documentation as comments within the WIT files themselves. It is very useful to read through the WIT comprehensively before using a new interface.
Take a look at /deps/http/types.wit. Here you'll find a detailed explanation of the interface's types. Note the outgoing-request type—it will come up again in a moment.
/// Represents an outgoing HTTP Request.
resource outgoing-request
With our WIT dependencies in place, we can specify interface imports and exports in world.wit. A WIT world is a set of standard interfaces that you can use to interact with components via defined functions and types. The WIT interfaces don't provide functionality in themselves—instead, they provide a common language for the contracts between components.
Remember that we targeted the hello world in our wasmcloud.toml file; this is where that world is defined. Let's update the hello world in world.wit:
package wasmcloud:hello;
world hello {
  import wasi:keyvalue/eventual@0.1.0; 
  export wasi:http/incoming-handler@0.2.0;
}
The wasmcloud.toml file points to the hello world in world.wit, and the hello world connects to our WIT definitions.
Now that our dependencies are defined, we're ready to get coding.
Working with WASI APIs in your Go code
Open hello.go.
package main
import (
	http "github.com/wasmcloud/wasmcloud/examples/golang/actors/http-hello-world/gen"
)
// Helper type aliases to make code more readable
type HttpRequest = http.ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest
type HttpResponseWriter = http.ExportsWasiHttp0_2_0_IncomingHandlerResponseOutparam
type HttpOutgoingResponse = http.WasiHttp0_2_0_TypesOutgoingResponse
type HttpError = http.WasiHttp0_2_0_TypesErrorCode
type HttpServer struct{}
func init() {
	httpserver := HttpServer{}
	// Set the incoming handler struct to HttpServer
	http.SetExportsWasiHttp0_2_0_IncomingHandler(httpserver)
}
func (h HttpServer) Handle(request HttpRequest, responseWriter HttpResponseWriter) {
	// Construct HttpResponse to send back
	headers := http.NewFields()
	httpResponse := http.NewOutgoingResponse(headers)
	httpResponse.SetStatusCode(200)
	httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8("Hello from Go!\n")).Unwrap()
	// Send HTTP response
	okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
	http.StaticResponseOutparamSet(responseWriter, okResponse)
}
//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
func main() {}
Most of this file is simple boilerplate. We'd like to use the WASI APIs that we've made available, but where can we learn how to use those APIs in Go?
The answer is in the penultimate line. When we build the application with the wash build command, the builder will use these instructions to run the built-in wit-bindgen tool and generate bindings between the functions we saw in the WIT dependencies and Go (specifically TinyGo).
Let's try it out. In the root of the project directory:
wash build
This will create a build directory with a compiled .wasm artifact for the app. It will also create a gen directory for bindings.
Open the new hello.go file in the gen directory. You will find a lengthy file defining the bindings between the functions and types in your WIT world and their implementations in Go. Take, for example, the outgoing-response type that we observed in types.wit. We can find the binding in gen/hello.go (Try searching the file for "outgoing-response"):
// WasiHttp0_2_0_TypesOutgoingResponse is a handle to imported resource outgoing-response
type WasiHttp0_2_0_TypesOutgoingResponse int32
Taken in conjunction, our WIT files and our generated bindings give us the tools we need to use the API and put the pieces together. If you return to our root hello.go project file, you'll notice that we're importing <project-name>/gen as http, so the Go file can access the WIT bindings for the WIT world. We've already used WasiHttp0_2_0_TypesOutgoingResponse, giving it the simpler shorthand of HttpOutgoingResponse.
What else do we have in our hello.go file?
First, we have three more helper types and a struct for the server. While we're here, let's add a helper type for OutgoingValue—if we've read through the wasi:keyvalue WIT, we know this will simplify key-value operations later on.
type HttpRequest = http.ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest
type HttpResponseWriter = http.ExportsWasiHttp0_2_0_IncomingHandlerResponseOutparam
type HttpOutgoingResponse = http.WasiHttp0_2_0_TypesOutgoingResponse
type HttpError = http.WasiHttp0_2_0_TypesErrorCode
type OutgoingValue = http.WasiKeyvalue0_1_0_TypesOutgoingValue
type HttpServer struct{}
In the init function, we have the HttpServer struct set as the handler for incoming requests:
func init() {
	httpserver := HttpServer{}
	// Set the incoming handler struct to HttpServer
	http.SetExportsWasiHttp0_2_0_IncomingHandler(httpserver)
}
Next we have a function responding to requests:
func (h HttpServer) Handle(request HttpRequest, responseWriter HttpResponseWriter) {
	// Construct HttpResponse to send back
	headers := http.NewFields()
	httpResponse := http.NewOutgoingResponse(headers)
	httpResponse.SetStatusCode(200)
	httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8("Hello from Go!\n")).Unwrap()
	// Send HTTP response
	okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
	http.StaticResponseOutparamSet(responseWriter, okResponse)
}
Finally, we have commented directions for wasmCloud's wash builder and the main function call.
Now, below the handler function, add a new function called getName—much like in the Quickstart. This will extract and return a name provided in incoming HTTP queries:
func getName(path string) string {
	parts := strings.Split(path, "=")
	if len(parts) == 2 {
		return parts[1]
	}
	return "Anonymous"
}
Call getName at the top of the handler function. We'll use the PathWithQuery method from the WASI HTTP API to grab the path and query from the incoming request:
// Get any name appended to the path via query
name := getName(request.PathWithQuery().Unwrap())
This is the first time we're actually implementing a new WASI method in this component, so we should pause to consider exactly what we're doing:
- We've defined 
requestas theHttpRequesttype at the top of the function, andHttpRequestis a helper alias forExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest. - When we look at our WIT bindings, we can see that 
ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequesthas a methodPathWithQuery. Both the bindings file and the original WIT tell us thatPathWithQueryreturns a string of the request path, including any query. However, the string is contained within a return object and needs to be "unwrapped." - To get at the actual value, we use the 
Unwrap()method. 
In order to guide our CRUD operations for the guestbook, we'd also like to know the method of the incoming request. We can detect that with the API as well. Try finding the method of the request with what you've learned so far.
If you guessed that you could use a method on request again, well done! Below the previous line add:
// Detect request method
method := request.Method()
This will return a method of the WasiHttp0_2_0_TypesMethod type, which we will be able to use directly.
We've implemented our basic HTTP operations. Now let's turn to key-value storage.
Adding CRUD
We can follow the same procedure we used for wasi-http to explore the wasi-keyvalue API and start adding CRUD operations.
In /wit/deps/keyvalue/types.wit, we see that collections of key-value pairs are referenced as buckets in the API:
/// A bucket is a collection of key-value pairs. Each key-value pair is stored
/// as a entry in the bucket, and the bucket itself acts as a collection of all
/// these entries.
///
/// It is worth noting that the exact terminology for bucket in key-value stores
/// can very depending on the specific implementation. For example,
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
/// 3. Cassandra calls a collection of key-value pairs a column family
/// 4. MongoDB calls a collection of key-value pairs a collection
/// 5. Riak calls a collection of key-value pairs a bucket
/// 6. Memcached calls a collection of key-value pairs a slab
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
///
/// In this interface, we use the term `bucket` to refer to a collection of key-value
Peruse the /gen/hello.go file to explore the Go bindings. (Searching bucket in the file is a quick way to find the relevant details.) We'd like to open a bucket to store the names of our guests, so we'll add the following to our handler function below the getName call:
// Open keyvaluestore bucket
// In the language of WASI, here we're creating a new bucket resource
bucket := http.StaticBucketOpenBucket("").Unwrap()
This is another case where we need to use the Unwrap() method, because on its own StaticBucketOpenBucket("") returns an object containing both the bucket and an error (if applicable).
Next we'll create a variable called outVal that will hold the new values assigned to keys in PUT/set operations. This is a wasi-keyvalue pattern that we would use in any language: the StaticOutgoingValueNewOutgoingValue() method is creating a new resource, which we can use to perform synchronous write operations.
// Create a variable for outgoing values - Put/sets will use this
// In the language of WASI, here we're creating a new outgoing-value resource
outVal := OutgoingValue(http.StaticOutgoingValueNewOutgoingValue())
Now we'll replace our existing response with a switch case that creates/updates, reads, or destroys guestbook records based on the HTTP method of the incoming request. For this rudimentary app, each case will send an appropriate HTTP response:
// Build the HttpResponse to send back
headers := http.NewFields()
httpResponse := http.NewOutgoingResponse(headers)
httpResponse.SetStatusCode(200)
switch method {
case http.WasiHttp0_2_0_TypesMethodGet():
	// If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
	none := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().IsNone()
	if none {
		// Send error message if value is none
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(name + " not found\n")).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	} else {
		result := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().Unwrap()
		// Take this result (of the IncomingValue type) and consume that value to get a uint8 byte array, which we will then send as part of http response
		incomingValue := http.StaticIncomingValueIncomingValueConsumeSync(result).Unwrap()
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush(incomingValue).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	}
case http.WasiHttp0_2_0_TypesMethodPut():
	// Write the desired value (as byte array) in our variable
	outVal.OutgoingValueWriteBodySync([]uint8(fmt.Sprintf("attending")))
	// Set the value
	http.WasiKeyvalue0_1_0_EventualSet(bucket, name, outVal)
	// Send HTTP response
	httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(fmt.Sprintf("Added " + name))).Unwrap()
	okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
	http.StaticResponseOutparamSet(responseWriter, okResponse)
case http.WasiHttp0_2_0_TypesMethodDelete():
	// If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
	none := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().IsNone()
	if none {
		// Send error message if value is none
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(name + " not found\n")).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	} else {
		result := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().Unwrap()
		// Delete the key value pair
		http.WasiKeyvalue0_1_0_EventualDelete(bucket, name)
		// Send HTTP response
		message := "It's like " + name + " was never there"
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(message + "\n")).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	}
default:
	message := "Try sending a GET, PUT, or DELETE"
	httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(message + "\n")).Unwrap()
	okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
	http.StaticResponseOutparamSet(responseWriter, okResponse)
}
Most of our application logic is happening in this block, so let's pause to consider a few important elements.
The condition for each case is the request method returned to the method variable. Within the conditionals, each CRUD operation runs against bucket and name. In the case of WasiKeyvalue0_1_0_EventualDelete, that's pretty straightforward, since no data is being passed. The set and get operations are just a touch more complicated.
For the set, we use the OutgoingValueWriteBodySync method to write the body of the value to outVal. The value must be written as a []uint8 byte array. With our value written to outVal, we can pass it into WasiKeyvalue0_1_0_EventualSet.
The get logic is similar but runs in reverse. WasiKeyvalue0_1_0_EventualGet gives us a result that must be unwrapped and then consumed with StaticIncomingValueIncomingValueConsumeSync, which gives us a byte array. Byte arrays are a common unit of data in WASI interfaces—note how we use them in HTTP responses as well. Since we have a byte array from our incoming value now, we can pass it directly to the HTTP response.
At this point, hello.go should look like this:
package main
import (
	"fmt"
	"strings"
	http "github.com/wasmcloud/wasmcloud/examples/golang/actors/http-hello-world/gen"
)
// Helper type aliases to make code more readable
type HttpRequest = http.ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest
type HttpResponseWriter = http.ExportsWasiHttp0_2_0_IncomingHandlerResponseOutparam
type HttpOutgoingResponse = http.WasiHttp0_2_0_TypesOutgoingResponse
type HttpError = http.WasiHttp0_2_0_TypesErrorCode
type OutgoingValue = http.WasiKeyvalue0_1_0_TypesOutgoingValue
type HttpServer struct{}
func init() {
	httpserver := HttpServer{}
	// Set the incoming handler struct to HttpServer
	http.SetExportsWasiHttp0_2_0_IncomingHandler(httpserver)
}
func (h HttpServer) Handle(request HttpRequest, responseWriter HttpResponseWriter) {
	// Get any name appended to the path via query
	name := getName(request.PathWithQuery().Unwrap())
	// Detect request method
	method := request.Method()
	// Open keyvaluestore bucket
	// In the language of WASI, here we're creating a new bucket resource
	bucket := http.StaticBucketOpenBucket("").Unwrap()
	// Create a variable for outgoing values - Put/sets will use this
	// In the language of WASI, here we're creating a new outgoing-value resource
	outVal := OutgoingValue(http.StaticOutgoingValueNewOutgoingValue())
	// Build the HttpResponse to send back
	headers := http.NewFields()
	httpResponse := http.NewOutgoingResponse(headers)
	httpResponse.SetStatusCode(200)
	switch method {
	case http.WasiHttp0_2_0_TypesMethodGet():
		// If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
		none := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().IsNone()
		if none {
			// Send error message if value is none
			httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(name + " not found\n")).Unwrap()
			okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
			http.StaticResponseOutparamSet(responseWriter, okResponse)
		} else {
			result := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().Unwrap()
			// Take this result (of the IncomingValue type) and consume that value to get a uint8 byte array, which we will then send as part of http response
			incomingValue := http.StaticIncomingValueIncomingValueConsumeSync(result).Unwrap()
			httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush(incomingValue).Unwrap()
			okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
			http.StaticResponseOutparamSet(responseWriter, okResponse)
		}
	case http.WasiHttp0_2_0_TypesMethodPut():
		// Write the desired value (as byte array) in our variable
		outVal.OutgoingValueWriteBodySync([]uint8(fmt.Sprintf("attending")))
		// Set the value
		http.WasiKeyvalue0_1_0_EventualSet(bucket, name, outVal)
		// Send HTTP response
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(fmt.Sprintf("Added " + name))).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	case http.WasiHttp0_2_0_TypesMethodDelete():
		// If the key doesn't exist, then the value will be returned as none, so we're checking for noneness to handle errors
		none := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().IsNone()
		if none {
			// Send error message if value is none
			httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(name + " not found\n")).Unwrap()
			okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
			http.StaticResponseOutparamSet(responseWriter, okResponse)
		} else {
			result := http.WasiKeyvalue0_1_0_EventualGet(bucket, name).Unwrap().Unwrap()
			// Delete the key value pair
			http.WasiKeyvalue0_1_0_EventualDelete(bucket, name)
			// Send HTTP response
			message := "It's like " + name + " was never there"
			httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(message + "\n")).Unwrap()
			okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
			http.StaticResponseOutparamSet(responseWriter, okResponse)
		}
	default:
		message := "Try sending a GET, PUT, or DELETE"
		httpResponse.Body().Unwrap().Write().Unwrap().BlockingWriteAndFlush([]uint8(message + "\n")).Unwrap()
		okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
		http.StaticResponseOutparamSet(responseWriter, okResponse)
	}
}
func getName(path string) string {
	parts := strings.Split(path, "=")
	if len(parts) == 2 {
		return parts[1]
	}
	return "Anonymous"
}
//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
func main() {}
Since we've made changes to hello.go, we'll build again to update the .wasm artifact:
wash build
Start Redis
Up front, we said that we don't have to worry about the underlying software that provides a capability until we deploy. Now it's time to start thinking about which capability providers we want to use. You can explore the list of available capability providers in the wasmCloud GitHub repo. For key-value storage, we'll use Redis.
Start a Redis server with either redis-server or Docker:
redis-server &
docker run -d --name redis -p 6379:6379 redis
Prepare for deployment
The last step to deploy our component is preparing the declarative manifest in wadm.yaml, which defines the desired state for our application when it is running on wasmCloud. This will look familiar if you've used container orchestrators like Kubernetes. The manifest included with the hello-world-tinygo template looks like this:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: tinygo-http-hello-world
  annotations:
    version: v0.0.1
    description: 'HTTP hello world demo in Golang (TinyGo), using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)'
    experimental: 'true'
spec:
  components:
    - name: http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the actor
        - type: spreadscaler
          properties:
            replicas: 1
        # Link the HTTP server, and inform it to listen on port 8080
        # on the local machine
        - type: linkdef
          properties:
            target: httpserver
            values:
              ADDRESS: 127.0.0.1:8080
    # Add a capability provider that mediates HTTP access
    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
The metadata fields provide naming and versioning for our application. We'll update the name to describe what we've built:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: tinygo-http-hello-world
  name: cruddy
  annotations:
    version: v0.0.1
    description: "HTTP hello world demo in Golang (TinyGo), using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)"
	description: "CRUD demo"
    experimental: "true"
You will also need to install npm and run npm install in the project directory, since wash depends on npm to compile TypeScript/JavaScript code to Wasm.
Creating a new component
In wasmCloud, an application component is a WebAssembly component dedicated to an application's creative logic. Typically, we will simply refer to this as a "component." Historically, wasmCloud referred to this piece of an application as an "actor," but that term is deprecated in favor of "component" and CLI commands will change as of v1.0.
With wash installed, you can run the following command in your shell to create a new component from a simple template:
wash new actor cruddy --git wasmcloud/wasmcloud --subfolder examples/typescript/actors/http-hello-world --branch 0.82-examples
This command instructs wash to create a new component called "cruddy". For our template, we're using the http-hello-world template from the 0.82 branch of the wasmCloud project repo on GitHub. The new actor command always creates new components from templates—if you don't specify one from the outset, wash will provide you with a set of options.
The repo we're using gives us the skeleton for a new component in TypeScript. Let's take a look at the cruddy directory:
.
├── README.md
├── build
├── http-hello-world.ts
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
├── types
├── wadm.yaml
├── wasmcloud.toml
└── wit
Here we have an http-hello-world.ts file for the project, a types directory that we'll touch on more in a moment, and an empty build directory, along with standard files for a TypeScript project like package.json. We also have three pieces that make up a wasmCloud project:
wadm.yamlis a declarative deployment manifest used bywadm.wasmcloud.tomlis a metadata and permissions configuration file for the application component.- The 
witdirectory holds WebAssembly Interface Type (WIT) definitions for standard APIs used across the Wasm ecosystem—we'll discuss these at greater length in a moment. 
Let's open wasmcloud.toml and have a look around:
name = "typescript-http-hello-world"
language = "typescript"
type = "actor"
version = "0.1.0"
[actor]
wit_world = "hello"
wasm_target = "wasm32-wasi-preview2"
build_command = "npm run build"
build_artifact = "dist/http-hello-world.wasm"
destination = "build/http_hello_world_s.wasm"
Here we have metadata for details like naming and versioning. For now, we're most interested in the [actor] fields:
wit_worldspecifies the specific "world" of APIs defined in WIT that this application can use.wasm_targetidentifies the version of WebAssembly to use as a compilation target—in this case, WebAssembly with the Wasm System Interface (WASI) 0.2build_commanddefines the custom build command that we will use to build a TypeScript-based componentbuild_artifactidentifies the initial compiled Wasm binary for your build command.destinationdefines the name and location of the final, signed Wasm artifact for the component.
Now let's take a look at the wit directory.
Understanding WIT dependencies
We will interact with the httpserver and keyvalue capabilities via WebAssembly System Interface (WASI) APIs—ecosystem-standard APIs used for communication with and between components.
WASI is especially useful for building and maintaining standard and popular libraries in an efficient and language-agnostic way—so developers can focus on writing simple, portable, idiomatic code.
Take a look at the contents of wit:
├── deps
├── deps.toml
└── world.wit
depsis a directory that holds WIT definitions.deps.tomlis a TOML configuration file used to manage WIT dependencies.world.witdefines the interfaces that comprise your WIT "world."
Our template has pre-populated the wit directory with relevant definitions. However, if you wish to select your WIT dependencies and populate the directory yourself, you can use the wit-deps tool.
Open deps.toml. Here we have specified interfaces that we wish to utilize in our app. WASI interfaces like wasi-http or wasi-keyvalue are available to browse in the WebAssembly project's GitHub repo.
WIT definitions contain documentation as comments within the WIT files themselves. It is very useful to read through the WIT comprehensively before using a new interface.
Take a look at /deps/http/types.wit. Here you'll find a detailed explanation of the interface's types. Note the outgoing-request type—it will come up again in a moment.
/// Represents an outgoing HTTP Request.
resource outgoing-request
With our WIT dependencies in place, we can specify interface imports and exports in world.wit. A WIT world is a set of standard interfaces that you can use to interact with components via defined functions and types. The WIT interfaces don't provide functionality in themselves—instead, they provide a common language for the contracts between components.
Remember that we targeted the hello world in our wasmcloud.toml file; this is where that world is defined. Let's update the hello world in world.wit:
package wasmcloud:hello;
world hello {
  import wasi:keyvalue/eventual@0.1.0; 
  export wasi:http/incoming-handler@0.2.0;
}
The wasmcloud.toml file points to the hello world in world.wit, and the hello world connects to our WIT definitions.
Now that our dependencies are defined, we're ready to get coding.
Working with WASI APIs in your TypeScript code
Open http-hello-world.ts.
import { IncomingRequest, ResponseOutparam, OutgoingResponse, Fields } from 'wasi:http/types@0.2.0';
// Implementation of wasi-http incoming-handler
//
// NOTE: To understand the types involved, take a look at wit/deps/http/types.wit
function handle(req: IncomingRequest, resp: ResponseOutparam) {
  // Start building an outgoing response
  const outgoingResponse = new OutgoingResponse(new Fields());
  // Access the outgoing response body
  let outgoingBody = outgoingResponse.body();
  // Create a stream for the response body
  let outputStream = outgoingBody.write();
  // // Write hello world to the response stream
  outputStream.blockingWriteAndFlush(
    new Uint8Array(new TextEncoder().encode('Hello from Typescript!\n')),
  );
  // Set the status code for the response
  outgoingResponse.setStatusCode(200);
  // Set the created response
  ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
}
export const incomingHandler = {
  handle,
};
Most of this file is simple boilerplate. We'd like to use the WASI APIs that we've made available, but where can we learn how to use those APIs in TypeScript?
We can find documented TypeScript bindings for some of the WASI APIs defined by our WIT files in the types directory. Currently, the bindings correspond to the hello WIT world as it was defined in the template with only the HTTP export. Typically, we would update the bindings to reflect our changes to the WIT world by running a build.
wash build
At the time of writing, the underlying JavaScript component-builder JCO does not generate types for wasi:keyvalue. TypeScript-based components using the wasi:keyvalue API still work, but understanding how to use the API can be a little more challenging. This is a known issue and will be resolved in a future JCO release. For now, you can tell the TypeScript compiler to ignore the missing types by adding //@ts-expect-error before each import statement. Simply including the import statement will allow the wasmCloud host to provide the functionality at runtime.
Note that the bindings are based on the imports and exports defined in your WIT world. If you were to update the WIT world again, you would need to generate bindings again.
In our types directory we have a variety of bindings in .d.ts files. Open types/wasi-http-types.d.ts. You will find definitions of the bindings between HTTP types in your WIT world and their implementations in TypeScript. Take, for example, the outgoing-response type that we observed in types.wit. We can find the binding in types/wasi-http-types.d.ts on lines 280-287:
/**
 * Construct an `outgoing-response`, with a default `status-code` of `200`.
 * If a different `status-code` is needed, it must be set via the
 * `set-status-code` method.
 *
 * * `headers` is the HTTP Headers for the Response.
 */
export { OutgoingResponse };
Taken in conjunction, our WIT files and our generated bindings give us the tools we need to use the API and put the pieces together. If you return to our root http-hello-world.ts project file, you'll notice that we're importing a variety of types from the hello world, including OutgoingResponse:
import { IncomingRequest, ResponseOutparam, OutgoingResponse, Fields } from 'wasi:http/types@0.2.0';
While we're here, let's import some more types that we will need later on. Note that we've added //@ts-expect-error before our wasi:keyvalue import statements per the warning above. (Also note that we're renaming wasi:keyvalue's delete function to eightysix, since the compiler doesn't like having a function named delete.)
import {
  IncomingRequest,
  ResponseOutparam,
  OutgoingResponse,
  Fields,
  Method, 
} from 'wasi:http/types@0.2.0';
//@ts-expect-error -- bindings not currently generated by JCO
import { Bucket, OutgoingValue, IncomingValue } from 'wasi:keyvalue/types@0.1.0';
//@ts-expect-error -- bindings not currently generated by JCO
import { set, get, delete as eightysix } from 'wasi:keyvalue/eventual@0.1.0';
Now let's look at the rest of the file. First, the handle function handles an incoming req (of the IncomingRequest type) and return a resp (of the ResponseOutparam type). Most of our app will take place within this function.
function handle(req: IncomingRequest, resp: ResponseOutparam);
Next we have the code for sending HTTP responses. Let's go ahead and delete that, since we'll be replacing it soon.
// Start building an outgoing response
const outgoingResponse = new OutgoingResponse(new Fields());
// Access the outgoing response body
let outgoingBody = outgoingResponse.body();
// Create a stream for the response body
let outputStream = outgoingBody.write();
// Write hello world to the response stream
outputStream.blockingWriteAndFlush(
  new Uint8Array(new TextEncoder().encode('Hello from Typescript!\n')),
);
// Set the status code for the response
outgoingResponse.setStatusCode(200);
// Set the created response
ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
Now, below the handle function and above the export for incomingHandler, add a new function called getNameFromPath—much like in the Quickstart. This will extract and return a name provided in incoming HTTP queries.
function getNameFromPath(path: string): string {
  const parts = path.split('=');
  if (parts.length == 2) {
    return parts[1];
  }
  return `Anonymous`;
}
Call getNameFromPath at the top of the handle function. We'll use the pathWithQuery() method from the WASI HTTP API on req to grab the path and query from the incoming request:
// Get the name
const name = getNameFromPath(req.pathWithQuery() || '');
This is the first time we're actually implementing a new WASI method in this component, so we should pause to consider exactly what we're doing:
reqis defined as theIncomingRequesttype at the top of the function.- When we look at our WIT bindings, we can see that 
IncomingRequesthas a methodpathWithQuery(). 
In order to guide our CRUD operations for the guestbook, we'd also like to know the method of the incoming request. We can detect that with the API as well. Try finding the method of the request with what you've learned so far.
If you guessed that you could use a method on req again, well done! Below the previous line add:
// Find the request method
const checkMethod: Method = req.method();
const method = checkMethod.tag;
The method() method uses the Method type. If we consult the wasi-http-types.d.ts WIT bindings, starting on line 373 we see how to interact with our method types. Using .tag will give us the method as a string—for example, get.
We've implemented our basic HTTP operations. Now let's turn to key-value storage.
Adding CRUD
Typically, we would follow the same procedure we used for wasi-http to explore the wasi-keyvalue API and start adding CRUD operations. This is the procedure we use in our other language walkthroughs, but because JCO doesn't currently generate bindings for keyvalue, we have to be a little more creative.
In /wit/deps/keyvalue/types.wit, we see that collections of key-value pairs are referenced as buckets in the API:
/// A bucket is a collection of key-value pairs. Each key-value pair is stored
/// as a entry in the bucket, and the bucket itself acts as a collection of all
/// these entries.
///
/// It is worth noting that the exact terminology for bucket in key-value stores
/// can very depending on the specific implementation. For example,
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
/// 3. Cassandra calls a collection of key-value pairs a column family
/// 4. MongoDB calls a collection of key-value pairs a collection
/// 5. Riak calls a collection of key-value pairs a bucket
/// 6. Memcached calls a collection of key-value pairs a slab
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
///
/// In this interface, we use the term `bucket` to refer to a collection of key-value
If you make an import error when building with JCO, you get a list of available imports, an excerpt of which we'll list here as a useful reference:
[static]bucket.openBucket, [static]outgoingValue.newOutgoingValue, [method]outgoingValue.outgoingValueWriteBodyAsync, [method]outgoingValue.outgoingValueWriteBodySync, [static]incomingValue.incomingValueConsumeSync, [static]incomingValue.incomingValueConsumeAsync, [method]incomingValue.incomingValueSize, [resourceDrop]bucket, [resourceDrop]outgoingValue, [resourceDrop]incomingValue
Hey, there's our Bucket with its openBucket method! You might recall that we already imported the Bucket type. Add the following below the method detection:
// Open the bucket
const bucket = Bucket.openBucket('');
Next we'll create a variable called value that will hold the new values assigned to keys in PUT/set operations. This is a wasi-keyvalue pattern that we would use in any language: the newOutgoingValue() method is creating a new resource, which we can use to perform synchronous write operations.
// Create new outgoing value resource
const value = OutgoingValue.newOutgoingValue();
Now we'll replace our existing response with a switch case that creates/updates, reads, or destroys guestbook records based on the HTTP method of the incoming request. For this rudimentary app, each case will send an appropriate HTTP response:
switch (
  method 
) {
  case `get`:
    const result = get(bucket, name);
    if (result === undefined) {
      outputStream.blockingWriteAndFlush(new Uint8Array(new TextEncoder().encode(`Not found\n`)));
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
    } else {
      // Take this result (of the IncomingValue type) and consume that value to get bytes, which we will then send as part of http response
      const incoming = IncomingValue.incomingValueConsumeSync(result);
      // Send the HTTP response. We don't need to do anything to the incoming value -- it's already the expected bytes.
      outputStream.blockingWriteAndFlush(incoming);
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
    }
  case `put`:
    // Write the actual value to the value resource
    value.outgoingValueWriteBodySync(new Uint8Array(new TextEncoder().encode(`attended`)));
    // Set it and forget it
    set(bucket, name, value);
    // Write and send the HTTP response
    outputStream.blockingWriteAndFlush(
      new Uint8Array(new TextEncoder().encode(`Added ${name}!\n`)),
    );
    outgoingResponse.setStatusCode(200);
    ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
    return;
  case `delete`:
    const delcheck = get(bucket, name);
    if (delcheck === undefined) {
      outputStream.blockingWriteAndFlush(
        new Uint8Array(new TextEncoder().encode(`${name} not found\n`)),
      );
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
    } else {
      // Delete the key
      eightysix(bucket, name);
      outputStream.blockingWriteAndFlush(
        new Uint8Array(new TextEncoder().encode(`It's like ${name} was never there\n`)),
      );
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
    }
  default:
    // Write hello world to the response stream, set the status code, and set response
    outputStream.blockingWriteAndFlush(
      new Uint8Array(new TextEncoder().encode(`Try a GET, PUT, or DELETE\n`)),
    );
    outgoingResponse.setStatusCode(200);
    ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
    return;
}
Most of our application logic is happening in this block, so let's pause to consider a few important elements.
The condition for each case is the request method returned to the method const. Within the cases, each CRUD operation runs as an imported function (get, set, or eightysix-formerly-known-as-delete) against bucket and name.
For the set, we use the outgoingValueWriteBodySync() method to write the body of the value to value. The value must be written as bytes. With our value written to value, we can pass it to set.
The get logic is similar but runs in reverse. get gives us a result that must be consumed with incomingValueConsumeSync, which gives us a byte result, which can be passed directly to the HTTP response.
At this point, http-hello-world.ts should look like this:
import {
  IncomingRequest,
  ResponseOutparam,
  OutgoingResponse,
  Fields,
  Method,
} from 'wasi:http/types@0.2.0';
//@ts-expect-error -- bindings not currently generated by JCO
import { Bucket, OutgoingValue, IncomingValue } from 'wasi:keyvalue/types@0.1.0';
//@ts-expect-error -- bindings not currently generated by JCO
import { set, get, delete as eightysix } from 'wasi:keyvalue/eventual@0.1.0';
function handle(req: IncomingRequest, resp: ResponseOutparam) {
  // Prepare an outgoing response
  const outgoingResponse = new OutgoingResponse(new Fields());
  const outgoingBody = outgoingResponse.body();
  const outputStream = outgoingBody.write();
  // Get the name
  const name = getNameFromPath(req.pathWithQuery() || '');
  // Find the request method
  const checkMethod: Method = req.method();
  const method = checkMethod.tag;
  // Open the bucket
  const bucket = Bucket.openBucket('');
  // Create new outgoing value resource
  const value = OutgoingValue.newOutgoingValue();
  switch (method) {
    case `get`:
      const result = get(bucket, name);
      if (result === undefined) {
        outputStream.blockingWriteAndFlush(new Uint8Array(new TextEncoder().encode(`Not found\n`)));
        outgoingResponse.setStatusCode(200);
        ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
        return;
      } else {
        // Take this result (of the IncomingValue type) and consume that value to get bytes, which we will then send as part of http response
        const incoming = IncomingValue.incomingValueConsumeSync(result);
        // Send the HTTP response. We don't need to do anything to the incoming value -- it's already the expected bytes.
        outputStream.blockingWriteAndFlush(incoming);
        outgoingResponse.setStatusCode(200);
        ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
        return;
      }
    case `put`:
      // Write the actual value to the value resource
      value.outgoingValueWriteBodySync(new Uint8Array(new TextEncoder().encode(`attended`)));
      // Set it and forget it
      set(bucket, name, value);
      // Write and send the HTTP response
      outputStream.blockingWriteAndFlush(
        new Uint8Array(new TextEncoder().encode(`Added ${name}!\n`)),
      );
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
    case `delete`:
      const delcheck = get(bucket, name);
      if (delcheck === undefined) {
        outputStream.blockingWriteAndFlush(
          new Uint8Array(new TextEncoder().encode(`${name} not found\n`)),
        );
        outgoingResponse.setStatusCode(200);
        ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
        return;
      } else {
        // Delete the key
        eightysix(bucket, name);
        outputStream.blockingWriteAndFlush(
          new Uint8Array(new TextEncoder().encode(`It's like ${name} was never there\n`)),
        );
        outgoingResponse.setStatusCode(200);
        ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
        return;
      }
    default:
      // Write hello world to the response stream, set the status code, and set response
      outputStream.blockingWriteAndFlush(
        new Uint8Array(new TextEncoder().encode(`Try a GET, PUT, or DELETE\n`)),
      );
      outgoingResponse.setStatusCode(200);
      ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
      return;
  }
}
function getNameFromPath(path: string): string {
  const parts = path.split('=');
  if (parts.length == 2) {
    return parts[1];
  }
  return `Anonymous`;
}
export const incomingHandler = {
  handle,
};
Since we've made changes to http-hello-world.ts, we'll build again to update the .wasm artifact:
wash build
Start Redis
Up front, we said that we don't have to worry about the underlying software that provides a capability until we deploy. Now it's time to start thinking about which capability providers we want to use. You can explore the list of available capability providers in the wasmCloud GitHub repo. For key-value storage, we'll use Redis.
Start a Redis server with either redis-server or Docker:
redis-server &
docker run -d --name redis -p 6379:6379 redis
Prepare for deployment
The last step to deploy our component is preparing the declarative manifest in wadm.yaml, which defines the desired state for our application when it is running on wasmCloud. This will look familiar if you've used container orchestrators like Kubernetes. The manifest included with the TypeScript template looks like this:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: typescript-http-hello-world
  annotations:
    version: v0.0.1
    description: 'Demo of Typescript HTTP hello world server'
    experimental: 'true'
spec:
  components:
    # (Capability Provider) mediates HTTP access
    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
    # (Actor) A test actor that returns a string for any given HTTP request
    - name: typescript-http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the actor
        - type: spreadscaler
          properties:
            replicas: 1
        # Link the HTTP server, and inform it to listen on port 8080
        # on the local machine
        - type: linkdef
          properties:
            target: httpserver
            values:
              ADDRESS: 127.0.0.1:8080
The metadata fields provide naming and versioning for our application. We'll update the name to describe what we've built:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: typescript-http-hello-world
  name: cruddy
  annotations:
    version: v0.0.1
    description: "Demo of Typescript HTTP hello world server"
	description: "CRUD demo"
    experimental: "true"
You will also need both Python 3.10 or later and pip. After you have those, you'll need to install componentize-py v0.12 or later, since wash depends on componentize-py to compile Python code to Wasm.
Creating a new component
In wasmCloud, an application component is a WebAssembly component dedicated to an application's creative logic. Typically, we will simply refer to this as a "component." Historically, wasmCloud referred to this piece of an application as an "actor," but that term is deprecated in favor of "component" and CLI commands will change as of v1.0.
With wash installed, you can run the following command in your shell to create a new component from a simple template:
wash new actor cruddy-py --git wasmcloud/wasmcloud --subfolder examples/python/actors/http-hello-world --branch 0.82-examples
This command instructs wash to create a new component called "cruddy-py". For our template, we're using the http-hello-world template from the 0.82 branch of the wasmCloud project repo on GitHub. The new actor command always creates new components from templates—if you don't specify one from the outset, wash will provide you with a set of options.
The repo we're using gives us the skeleton for a new actor in Python. Let's take a look at the cruddy-py directory:
├── app.py
├── build
├── wadm.yaml
├── wasmcloud.toml
└── wit
Here we have an app.py file for the project and an empty build directory. We also have three pieces that make up a wasmCloud project:
wadm.yamlis a declarative deployment manifest used bywadm.wasmcloud.tomlis a metadata and permissions configuration file for the application component.- The 
witdirectory holds WebAssembly Interface Type (WIT) definitions for standard APIs used across the Wasm ecosystem—we'll discuss these at greater length in a moment. 
Let's open wasmcloud.toml and have a look around:
name = "python-http-hello-world"
language = "python"
type = "component"
version = "0.1.0"
[component]
build_command = "componentize-py -d ./wit -w hello componentize app -o build/http_hello_world.wasm"
build_artifact = "build/http_hello_world.wasm"
destination = "build/http_hello_world_s.wasm"
Here we have metadata for details like naming and versioning. For now, we're most interested in the three [component] fields:
build_commanddefines the custom build command that we will use to build a Python-based component usingcomponentize-pybuild_artifactidentifies the initial compiled Wasm binary for your build command.destinationdefines the name and location of the final, signed Wasm artifact for the component.
Now let's take a look at the wit directory.
Understanding WIT dependencies
We will interact with the httpserver and keyvalue capabilities via WebAssembly System Interface (WASI) APIs—ecosystem-standard APIs used for communication with and between components.
WASI is especially useful for building and maintaining standard and popular libraries in an efficient and language-agnostic way—so developers can focus on writing simple, portable, idiomatic code.
Take a look at the contents of wit:
├── deps
├── deps.toml
└── world.wit
depsis a directory that holds WIT definitions.deps.tomlis a TOML configuration file used to manage WIT dependencies.world.witdefines the interfaces that comprise your WIT "world."
Our template has pre-populated the wit directory with relevant definitions. However, if you wish to select your WIT dependencies and populate the directory yourself, you can use the wit-deps tool.
Open deps.toml. Here we have specified interfaces that we wish to utilize in our app. WASI interfaces like wasi-http or wasi-keyvalue are available to browse in the WebAssembly project's GitHub repo.
WIT definitions contain documentation as comments within the WIT files themselves. It is very useful to read through the WIT comprehensively before using a new interface.
Take a look at /deps/http/types.wit. Here you'll find a detailed explanation of the interface's types. Note the outgoing-request type—it will come up again in a moment.
/// Represents an outgoing HTTP Request.
resource outgoing-request
With our WIT dependencies in place, we can specify interface imports and exports in world.wit. A WIT world is a set of standard interfaces that you can use to interact with components via defined functions and types. The WIT interfaces don't provide functionality in themselves—instead, they provide a common language for the contracts between components.
Remember that we targeted the hello world in our wasmcloud.toml file; this is where that world is defined. Let's update the hello world in world.wit:
package wasmcloud:hello;
world hello {
  import wasi:keyvalue/eventual@0.1.0; 
  export wasi:http/incoming-handler@0.2.0;
}
The wasmcloud.toml file points to the hello world in world.wit, and the hello world connects to our WIT definitions.
Now that our dependencies are defined, we're ready to get coding.
Working with WASI APIs in your Python code
Open app.py.
from hello import exports
from hello.types import Ok
from hello.imports.types import (
    IncomingRequest, ResponseOutparam,
    OutgoingResponse, Fields, OutgoingBody
)
class IncomingHandler(exports.IncomingHandler):
    def handle(self, _: IncomingRequest, response_out: ResponseOutparam):
        # Construct the HTTP response to send back
        outgoingResponse = OutgoingResponse(Fields.from_list([]))
        # Set the status code to OK
        outgoingResponse.set_status_code(200)
        outgoingBody = outgoingResponse.body()
        # Write our Hello World message to the response body
        outgoingBody.write().blocking_write_and_flush(bytes("Hello from Python!\n", "utf-8"))
        OutgoingBody.finish(outgoingBody, None)
        # Set and send the HTTP response
        ResponseOutparam.set(response_out, Ok(outgoingResponse))
Most of this file is simple boilerplate. We'd like to use the WASI APIs that we've made available, but where can we learn how to use those APIs in Python?
componentize-py makes it easy to generate documented Python bindings for the WASI APIs defined by our WIT files. This will also enable IDE suggestions and autofills, providing a smoother development experience. To generate the bindings:
componentize-py bindings .
Note that the bindings will be based on the imports and exports defined in your WIT world. If you were to update the WIT world again, you would need to generate bindings again.
Now you have a new hello directory with bindings in .py files. Open hello/imports/types.py. You will find definitions of the bindings between types in your WIT world and their implementations in Python. Take, for example, the outgoing-response type that we observed in types.wit. We can find the binding in hello/imports/types.py (Try searching the file for "OutgoingResponse"):
class OutgoingResponse:
    """
    Represents an outgoing HTTP Response.
    """
    def __init__(self, headers: Fields):
        """
        Construct an `outgoing-response`, with a default `status-code` of `200`.
        If a different `status-code` is needed, it must be set via the
        `set-status-code` method.
        * `headers` is the HTTP Headers for the Response.
        """
        raise NotImplementedError
    def status_code(self) -> int:
        """
        Get the HTTP Status Code for the Response.
        """
        raise NotImplementedError
    def set_status_code(self, status_code: int) -> None:
        """
        Set the HTTP Status Code for the Response. Fails if the status-code
        given is not a valid http status code.
        Raises: `hello.types.Err(None)`
        """
        raise NotImplementedError
Taken in conjunction, our WIT files and our generated bindings give us the tools we need to use the API and put the pieces together. If you return to our root app.py project file, you'll notice that we're importing a variety of types from the hello world, including OutgoingResponse:
from hello.imports.types import (
    IncomingRequest, ResponseOutparam,
    OutgoingResponse, Fields, OutgoingBody
)
While we're here, let's import some more types and some key-value functions that we will need later on:
from hello.imports.types import (
    IncomingRequest, ResponseOutparam,
    OutgoingResponse, Fields, OutgoingBody,
    Bucket, OutgoingValue, IncomingValue, MethodGet, MethodPut, MethodDelete 
)
from hello.imports.eventual import (set, get, delete) 
Now let's look at the rest of the file. First, an IncomingHandler is instantiated with a handle function inside set to handle an incoming request (of the IncomingRequest type) and return a response (of the ResponseOutparam type). Most of our app will take place within this function.
class IncomingHandler(exports.IncomingHandler):
    def handle(self, _: IncomingRequest, response_out: ResponseOutparam):
Next we have the code for sending HTTP responses. Let's go ahead and delete that, since we'll be replacing it soon.
# Construct the HTTP response to send back
outgoingResponse = OutgoingResponse(Fields.from_list([])) 
# Set the status code to OK
outgoingResponse.set_status_code(200) 
outgoingBody = outgoingResponse.body() 
# Write our Hello World message to the response body
outgoingBody.write().blocking_write_and_flush(bytes("Hello from Python!\n", "utf-8")) 
OutgoingBody.finish(outgoingBody, None) 
# Set and send the HTTP response
ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
Now, outside and below the IncomingHandler object, add a new function called getName—much like in the Quickstart. This will extract and return a name provided in incoming HTTP queries.
# Helper function to get name
def get_name_from_path(path: str) -> str: 
    parts = path.split("=") 
    if len(parts) == 2: 
        return parts[-1] 
    else: 
        return "Anonymous"
Call get_name_from_path at the top of the handler function. We'll use the path_with_query() method from the WASI HTTP API to grab the path and query from the incoming request:
# Get the name
name = get_name_from_path(request.path_with_query()) 
This is the first time we're actually implementing a new WASI method in this component, so we should pause to consider exactly what we're doing:
requestis defined as theIncomingRequesttype at the top of the function.- When we look at our WIT bindings, we can see that 
IncomingRequesthas a methodpath_with_query(). (Our IDE autofill can surface the method even more easily.) 
In order to guide our CRUD operations for the guestbook, we'd also like to know the method of the incoming request. We can detect that with the API as well. Try finding the method of the request with what you've learned so far.
If you guessed that you could use a method on request again, well done! Below the previous line add:
# Detect request method
method = request.method() 
This will return a method of one of the types we imported earlier (MethodGet, MethodPut, or MethodDelete).
We've implemented our basic HTTP operations. Now let's turn to key-value storage.
Adding CRUD
We can follow the same procedure we used for wasi-http to explore the wasi-keyvalue API and start adding CRUD operations.
In /wit/deps/keyvalue/types.wit, we see that collections of key-value pairs are referenced as buckets in the API:
/// A bucket is a collection of key-value pairs. Each key-value pair is stored
/// as a entry in the bucket, and the bucket itself acts as a collection of all
/// these entries.
///
/// It is worth noting that the exact terminology for bucket in key-value stores
/// can very depending on the specific implementation. For example,
/// 1. Amazon DynamoDB calls a collection of key-value pairs a table
/// 2. Redis has hashes, sets, and sorted sets as different types of collections
/// 3. Cassandra calls a collection of key-value pairs a column family
/// 4. MongoDB calls a collection of key-value pairs a collection
/// 5. Riak calls a collection of key-value pairs a bucket
/// 6. Memcached calls a collection of key-value pairs a slab
/// 7. Azure Cosmos DB calls a collection of key-value pairs a container
///
/// In this interface, we use the term `bucket` to refer to a collection of key-value
Peruse the /hello/imports/types.py file to explore the Python bindings. (Searching bucket in the file is a quick way to find the relevant details.) You might also recall that we imported the Bucket type. The syntax to open a bucket is pretty intuitive—add the following below the method detection:
# Open the bucket
bucket = Bucket.open_bucket("") 
Next we'll create a variable called value that will hold the new values assigned to keys in PUT/set operations. This is a wasi-keyvalue pattern that we would use in any language: the new_outgoing_value() method is creating a new resource, which we can use to perform synchronous write operations.
# Create new outgoing value resource, then write the actual value
value = OutgoingValue.new_outgoing_value() 
Now we'll replace our existing response with a conditional sequence that creates/updates, reads, or destroys guestbook records based on the HTTP method of the incoming request. For this rudimentary app, each case will send an appropriate HTTP response:
# Begin conditional sequence on method
        if type(method) == MethodGet: 
            # Get the value for this key (will return None if not found)
            result = get(bucket, name) 
            # Checking for noneness to handle errors
            if result == None: 
                # Send error message if the value is none
                outgoingBody.write().blocking_write_and_flush(bytes("{} not found\n".format(name), "utf-8")) 
                OutgoingBody.finish(outgoingBody, None) 
                ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
            else: 
                # Take this result (of the IncomingValue type) and consume that value to get a byte array, which we will then send as part of http response
                incoming = IncomingValue.incoming_value_consume_sync(result) 
                # Send the HTTP response. We don't need to do anything to the incoming value -- it's already the expected byte array.
                outgoingBody.write().blocking_write_and_flush(incoming) 
                OutgoingBody.finish(outgoingBody, None) 
                ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
        elif type(method) == MethodPut: 
            # Write the actual value to the value resource
            value.outgoing_value_write_body_sync(bytes("attended\n", "utf-8")) 
            # Set it and forget it
            set(bucket, name, value) 
            # Write and send the HTTP response
            outgoingBody.write().blocking_write_and_flush(bytes("Added {}!\n".format(name), "utf-8")) 
            OutgoingBody.finish(outgoingBody, None) 
            ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
        elif type(method) == MethodDelete: 
            # Get the value for this key (will return None if not found)
            result = get(bucket, name) 
            # Checking for noneness to handle errors
            if result == None: 
                # Send error message if the value is none
                outgoingBody.write().blocking_write_and_flush(bytes("{} not found\n".format(name), "utf-8")) 
                OutgoingBody.finish(outgoingBody, None) 
                ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
            else: 
                # Delete the key
                delete(bucket, name) 
                # Write and send the HTTP response
                outgoingBody.write().blocking_write_and_flush(bytes("It's like {} was never there\n".format(name), "utf-8")) 
                OutgoingBody.finish(outgoingBody, None) 
                ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
        else: 
             # Write and send the HTTP response
            outgoingBody.write().blocking_write_and_flush(bytes("Try a GET, PUT, or DELETE\n".format(name), "utf-8")) 
            OutgoingBody.finish(outgoingBody, None) 
            ResponseOutparam.set(response_out, Ok(outgoingResponse)) 
Most of our application logic is happening in this block, so let's pause to consider a few important elements.
The condition for each case is the request method returned to the method variable as a type. Within the conditionals, each CRUD operation runs as an imported function (get, set, or delete) against bucket and name. In the case of delete, that's extremely straightforward, since no data is being passed. The set and get operations are just a touch more complicated.
For the set, we use the outgoing_value_write_body_sync() method to write the body of the value to value. The value must be written as bytes. With our value written to value, we can pass it to set.
The get logic is similar but runs in reverse. get gives us a result that must be consumed with incoming_value_consume_sync, which gives us a byte result, which can be passed directly to the HTTP response.
At this point, app.py should look like this:
from hello import exports
from hello.types import Ok
from hello.imports.types import (
    IncomingRequest, ResponseOutparam,
    OutgoingResponse, Fields, OutgoingBody, Bucket,
    OutgoingValue, IncomingValue, MethodGet, MethodPut, MethodDelete
)
from hello.imports.eventual import (set, get, delete)
class IncomingHandler(exports.IncomingHandler):
    def handle(self, request: IncomingRequest, response_out: ResponseOutparam):
        # Construct the HTTP response to send back
        outgoingResponse = OutgoingResponse(Fields.from_list([]))
        outgoingResponse.set_status_code(200)
        outgoingBody = outgoingResponse.body()
        # Get the name
        name = get_name_from_path(request.path_with_query())
        # Detect request method
        method = request.method()
        # Open the bucket
        bucket = Bucket.open_bucket("")
        # Create new outgoing value resource
        value = OutgoingValue.new_outgoing_value()
        # Begin conditional sequence on method
        if type(method) == MethodGet:
            # Get the value for this key (will return None if not found)
            result = get(bucket, name)
            # Checking for noneness to handle errors
            if result == None:
                # Send error message if the value is none
                outgoingBody.write().blocking_write_and_flush(bytes("{} not found\n".format(name), "utf-8"))
                OutgoingBody.finish(outgoingBody, None)
                ResponseOutparam.set(response_out, Ok(outgoingResponse))
            else:
                # Take this result (of the IncomingValue type) and consume that value to get bytes, which we will then send as part of http response
                incoming = IncomingValue.incoming_value_consume_sync(result)
                # Send the HTTP response. We don't need to do anything to the incoming value -- it's already the expected bytes.
                outgoingBody.write().blocking_write_and_flush(incoming)
                OutgoingBody.finish(outgoingBody, None)
                ResponseOutparam.set(response_out, Ok(outgoingResponse))
        elif type(method) == MethodPut:
            # Write the actual value to the value resource
            value.outgoing_value_write_body_sync(bytes("attended\n", "utf-8"))
            # Set it and forget it
            set(bucket, name, value)
            # Write and send the HTTP response
            outgoingBody.write().blocking_write_and_flush(bytes("Added {}!\n".format(name), "utf-8"))
            OutgoingBody.finish(outgoingBody, None)
            ResponseOutparam.set(response_out, Ok(outgoingResponse))
        elif type(method) == MethodDelete:
            # Get the value for this key (will return None if not found)
            result = get(bucket, name)
            # Checking for noneness to handle errors
            if result == None:
                # Send error message if the value is none
                outgoingBody.write().blocking_write_and_flush(bytes("{} not found\n".format(name), "utf-8"))
                OutgoingBody.finish(outgoingBody, None)
                ResponseOutparam.set(response_out, Ok(outgoingResponse))
            else:
                # Delete the key
                delete(bucket, name)
                # Write and send the HTTP response
                outgoingBody.write().blocking_write_and_flush(bytes("It's like {} was never there\n".format(name), "utf-8"))
                OutgoingBody.finish(outgoingBody, None)
                ResponseOutparam.set(response_out, Ok(outgoingResponse))
        else:
             # Write and send the HTTP response
            outgoingBody.write().blocking_write_and_flush(bytes("Try a GET, PUT, or DELETE\n".format(name), "utf-8"))
            OutgoingBody.finish(outgoingBody, None)
            ResponseOutparam.set(response_out, Ok(outgoingResponse))
# Helper function to get name
def get_name_from_path(path: str) -> str:
    parts = path.split("=")
    if len(parts) == 2:
        return parts[-1]
    else:
        return "Anonymous"
Since we've made changes to app.py, we'll build again to update the .wasm artifact:
wash build
Start Redis
Up front, we said that we don't have to worry about the underlying software that provides a capability until we deploy. Now it's time to start thinking about which capability providers we want to use. You can explore the list of available capability providers in the wasmCloud GitHub repo. For key-value storage, we'll use Redis.
Start a Redis server with either redis-server or Docker:
redis-server &
docker run -d --name redis -p 6379:6379 redis
Prepare for deployment
The last step to deploy our component is preparing the declarative manifest in wadm.yaml, which defines the desired state for our application when it is running on wasmCloud. This will look familiar if you've used container orchestrators like Kubernetes. The manifest included with the hello-world-python template looks like this:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: python-http-hello-world
  annotations:
    version: v0.0.1
    description: 'Python component that uses wasi:http'
    experimental: 'true'
spec:
  components:
    - name: python-http
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        # Govern the spread/scheduling of the actor
        - type: spreadscaler
          properties:
            replicas: 1
        # Link the HTTP server, and inform it to listen on port 8081
        # on the local machine
        - type: linkdef
          properties:
            target: httpserver
            values:
              ADDRESS: 127.0.0.1:8080
    # Add a capability providers that mediates HTTP access
    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
The metadata fields provide naming and versioning for our application. We'll update the name to describe what we've built:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: python-http-hello-world
  name: cruddy
  annotations:
    version: v0.0.1
    description: 'Python component that uses wasi:http'
	description: "CRUD demo"
    experimental: "true"
Every time we publish a new version, we'll need to increment (or otherwise change) the version annotation here.
Next in the spec, we have our components: the http-hello-world templated component that we've been working on, and the httpserver capability fulfilled by a capability provider. We'll explore capabilities more in a moment, but first, we'll give the component a link to the keyvalue capability, specifying the address for the Redis service:
spec:
  components:
    - name: http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        - type: spreadscaler
          properties:
            replicas: 1
        - type: linkdef
          properties:
            target: httpserver
            values:
              address: 0.0.0.0:8080
        - type: linkdef
          properties: 
            target: keyvalue
            values: 
              address: redis://127.0.0.1:6379
Finally, we'll add the keyvalue capability provider as the final component that makes up our application. As part of this entry, we'll specify that the component—provided through the defined image—is fulfilling the contract for the keyvalue capability. wasmCloud operates on a zero-trust security model: without being explicitly linked to a capability, the application component would have no access to it.
- name: httpserver
  type: capability
  properties:
    image: wasmcloud.azurecr.io/httpserver:0.19.1
    contract: wasmcloud:httpserver
- name: keyvalue
  type: capability
  properties: 
    image: wasmcloud.azurecr.io/kvredis:0.22.0
    contract: wasmcloud:keyvalue
We effectively promised our application component that someone would get the job done, and now these first-party providers (developed as part of the wasmCloud project) are stepping up to fill the role. A different provider—first-party, third-party, or of original design—could just as easily do the same. The same component we wrote could perform CRUDdy operations against any number of different key-value stores—etcd, MongoDB, Cassandra, or your own—as long as a provider exists for it. If a provider doesn't exist yet, you have all the tools you need to create one.
Once you put all of the pieces of wadm.yaml together, the file should look like this:
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
  name: cruddy
  annotations:
    version: v0.0.1
    description: 'CRUD demo'
    experimental: 'true'
spec:
  components:
    - name: http-hello-world
      type: actor
      properties:
        image: file://./build/http_hello_world_s.wasm
      traits:
        - type: spreadscaler
          properties:
            replicas: 1
        - type: linkdef
          properties:
            target: httpserver
            values:
              address: 0.0.0.0:8080
        - type: linkdef
          properties:
            target: keyvalue
            values:
              address: redis://127.0.0.1:6379
    - name: httpserver
      type: capability
      properties:
        image: wasmcloud.azurecr.io/httpserver:0.19.1
        contract: wasmcloud:httpserver
    - name: keyvalue
      type: capability
      properties:
        image: wasmcloud.azurecr.io/kvredis:0.22.0
        contract: wasmcloud:keyvalue
Launch and iterate
Start a wasmCloud host with wash up. Now we're ready to launch our app:
wash app deploy wadm.yaml
To view your wasmCloud apps and check status:
wash app list
Once the app status is Deployed, we can test a PUT against our app with curl:
curl -X PUT "localhost:8080?name=Alice"
We should get the result:
Added Alice
We can test a GET and DELETE as well:
curl "localhost:8080?name=Alice"
attended
curl -X DELETE "localhost:8080?name=Alice"
It's like Alice was never there
If we want to update our application, we can wash build, update the version in wadm.yaml, and wash app deploy wadm.yaml again.
Note that wasmCloud includes an experimental feature for dev loop iteration. Rather than wash app deploy, from your project directory you can run:
wash dev --experimental --host-id=<your-host-id>
This will start a dev deployment that continuously watches for changes to the .wasm file for your component. If you would like to try this experimental feature, first clean up your existing deployment according to the instructions below.
Clean up
Once you're finished working with these tutorial materials, undeploy the application to stop it from running on the wasmCloud host and delete active links, freeing up associated ports:
wash app undeploy cruddy
To completely remove all versions of the application from wasmCloud:
wash app delete cruddy --delete-all
Next steps
In this tutorial, you've learned how to create a guestbook application component that uses WASI APIs including HTTP and Key-Value. In doing so, you've also learned how to read the WIT definitions for those APIs and utilize idiomatic bindings for our chosen language. With these fundamentals in place, a good next step might be to try building more complex components, explore other WASI APIs, or create your own interfaces and providers. If you have questions or feedback, join the wasmCloud Slack and let us know. Happy coding!