Skip to main content
Version: 1.x

Add Features

Going from "Hello World" to a more complex application starts with two steps:

  • Identify the capabilities your application needs. (Think common requirements like serving HTTP, storing key-value pairs, or logging.)
  • Add interfaces for those capabilities.

When you're writing a wasmCloud application, you don't have to worry about how a capability is fulfilled as long as you're writing to a standard interface—you can simply focus on your code.

In this tutorial, we'll add more features to our application by plugging in key-value and logging capabilities.

Prerequisites

This tutorial assumes you're following directly from the previous tutorial. If you don't have a "Hello world" application running with wash dev, complete Quickstart first.

Add functionality

Let's extend this application to do more than just say "Hello!"

Using the FormValue method on the incoming request, we can check the request for a name provided in a query string, and then return a greeting with that name. If there isn't one or the path isn't in the format we expect, we'll default to saying "Hello, World!"

go
//go:generate go run github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go generate --world hello --out gen ./wit
import (
  "fmt"
  "net/http"

  "go.wasmcloud.dev/component/net/wasihttp"
)

func init() {
  wasihttp.HandleFunc(handleRequest)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
  name := "World"
  if len(r.FormValue("name")) > 0 { 
    name = r.FormValue("name") 
  } 
  fmt.Fprintf(w, "Hello, %s!\n", name) 
  fmt.Fprintf(w, "Hello from Go!\n") 
}

// Since we don't run this program like a CLI, the `main` function is empty. Instead,
// we call the `handleRequest` function when an HTTP request is received.
func main() {}

After saving your changes, wash dev automatically builds and runs the updated application.

We can curl the application again:

shell
curl localhost:8000
text
Hello, World!
shell
curl 'localhost:8000?name=Bob'
text
Hello, Bob!

Add persistent storage

Now let's add persistent storage to keep a record of each person that this application greeted.

We'll use the key-value capability for this. We don't need to pick a library or a specific vendor implementation—all we have to do is add the interface to our component.

We can use the wasi:keyvalue interface for interacting with a key value store, and the wasi:logging interface to log the name of each person we greet. Before we can use those interfaces, we'll need to add them to our wit/world.wit file:

wit
package wasmcloud:hello;

world hello {
  include wasmcloud:component-go/imports@0.1.0;
  import wasi:keyvalue/atomics@0.2.0-draft; 
  import wasi:keyvalue/store@0.2.0-draft; 

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

We've given our application the ability to perform atomic incrementation and storage operations via the wasi:keyvalue interface.

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

go
//go:generate go run github.com/bytecodealliance/wasm-tools-go/cmd/wit-bindgen-go generate --world hello --out gen ./wit
package main

import (
  "fmt"
  "net/http"

  atomics "github.com/wasmcloud/wasmcloud/examples/golang/components/http-hello-world/gen/wasi/keyvalue/atomics"
  store "github.com/wasmcloud/wasmcloud/examples/golang/components/http-hello-world/gen/wasi/keyvalue/store"
  "go.wasmcloud.dev/component/log/wasilog"
  "go.wasmcloud.dev/component/net/wasihttp"
)

func init() {
  wasihttp.HandleFunc(handleRequest)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
  logger := wasilog.ContextLogger("handleRequest")  

  name := "World"
  if len(r.FormValue("name")) > 0 {
    name = r.FormValue("name")
  }
  logger.Info("Greeting", "name", name) 

  kvStore := store.Open("default") 
  if err := kvStore.Err(); err != nil {
    w.Write([]byte("Error: " + err.String()))
    return
  }
  value := atomics.Increment(*kvStore.OK(), name, 1)
  if err := value.Err(); err != nil {
    w.Write([]byte("Error: " + err.String()))
    return
  }

  fmt.Fprintf(w, "Hello x%d, %s!\n", *value.OK(), name) 
  fmt.Fprintf(w, "Hello, %s!\n", name) 
}

// Since we don't run this program like a CLI, the `main` function is empty. Instead,
// we call the `handleRequest` function when an HTTP request is received.
func main() {}

Download the wasilog module for idiomatic Go logging that compiles to a component:

shell
go get go.wasmcloud.dev/component/log/wasilog

We've made changes, so once we save, wash dev will once again automatically update the running application.

shell
curl 'localhost:8000?name=Bob'
text
Hello x1, Bob!
shell
curl 'localhost:8000?name=Bob'
text
Hello x2, Bob!
shell
curl 'localhost:8000?name=Alice'
text
Hello x1, Alice!

Next steps

In this tutorial, you added a few more features and persistent storage to a simple microservice. You also got to see the process of developing with capabilities, where you can...

  • Write purely functional code that doesn't require you to pick a library or vendor upfront
  • Change your application separately from its non-functional requirements

So far, the wash dev process has satisfied our application's capability requirements automatically, so we can move quickly and focus on code. In the next tutorial, we'll look under the hood and learn how to extend and deploy applications manually, plugging in different providers to deliver our capabilities.