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 PathWithQuery 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!"

To make this code more readable, we'll add a helper function to extract the name from the path.

go
package main

import (
  "fmt"
  "strings"
  http "github.com/wasmcloud/wasmcloud/examples/golang/components/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)

  body := httpResponse.Body().Unwrap()
  bodyWrite := body.Write().Unwrap()
  bodyWrite.BlockingWriteAndFlush([]uint8("Hello from Go!\n")).Unwrap() 
  name := getNameFromPath(request.PathWithQuery().Unwrap()) 
  bodyWrite.BlockingWriteAndFlush([]uint8(fmt.Sprintf("Hello %s!\n", name))).Unwrap() 

  // Send HTTP response
  okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
  bodyWrite.Drop()
  http.StaticOutgoingBodyFinish(body, http.None[http.WasiHttp0_2_0_TypesTrailers]())
  http.StaticResponseOutparamSet(responseWriter, okResponse)
}

func getNameFromPath(path string) string { 
  parts := strings.Split(path, "=")
  if len(parts) == 2 {
    return parts[1]
  }
  return "World"
}

//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
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:8080?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 {
  import wasi:keyvalue/atomics@0.2.0-draft; 
  import wasi:keyvalue/store@0.2.0-draft; 
  import wasi:logging/logging; 

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

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

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

go
func (h HttpServer) Handle(request HttpRequest, responseWriter HttpResponseWriter) {
  // Construct HttpResponse to send back
  headers := http.NewFields()
  httpResponse := http.NewOutgoingResponse(headers)
  httpResponse.SetStatusCode(200)

  body := httpResponse.Body().Unwrap()
  bodyWrite := body.Write().Unwrap()
  path := request.PathWithQuery().Unwrap() 

  http.WasiLoggingLoggingLog(http.WasiLoggingLoggingLevelInfo(), "", fmt.Sprintf("Greeting %s", name))
  bucket := http.WasiKeyvalue0_2_0_draft_StoreOpen("").Unwrap()
  count := http.WasiKeyvalue0_2_0_draft_AtomicsIncrement(bucket, path, 1).Unwrap()

  bodyWrite.BlockingWriteAndFlush([]uint8(fmt.Sprintf("Hello x%d, %s!\n", count, name))).Unwrap()
  bodyWrite.BlockingWriteAndFlush([]uint8("Hello from Go!\n")).Unwrap() 

  // Send HTTP response
  okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
  bodyWrite.Drop()
  http.StaticOutgoingBodyFinish(body, http.None[http.WasiHttp0_2_0_TypesTrailers]())
  http.StaticResponseOutparamSet(responseWriter, okResponse)
}

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

shell
curl 'localhost:8080?name=Bob'
text
Hello x1, Bob!
shell
curl 'localhost:8080?name=Bob'
text
Hello x2, Bob!
shell
curl 'localhost:8080?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.