April 14, 2023

Introducing Spin 1.1

Radu Matei Radu Matei

Introducing Spin 1.1

A few weeks ago, we introduced Spin 1.0, the first stable release of Spin, with the goal of creating a frictionless experience for developers who want to build, distribute, and run serverless applications with WebAssembly.

Today, we are happy to announce Spin 1.1, which brings a few improvements:

  • a new spin watch command that watches, rebuilds, and restarts your application when certain files change
  • the option to seamlessly use a new implementation for the Spin key/value store by configuring a Redis instance instead of Spin’s built-in store
  • new routers in the Rust and Go SDKs to simplify building HTTP APIs
  • Spin now executes WebAssembly components, and is fully compatible with the WebAssembly component model
  • a host of bugfixes and improvements

spin watch

One of the most common patterns when iterating on an application is:

  1. Make some changes to your source code
  2. Rebuild your application
  3. Start the application
  4. Go to step 1.

In Spin 1.1, we are introducing a new top-level command to address this scenario — spin watch, which will rebuild and restart your Spin application when certain files change. The watch configuration is added in the existing component.build section, and can contain individual files and patterns (similar to the patterns used when mounting files in your Spin applications). Let’s have a look at an example for a Go component that has a watch configuration:

[component.build]
command = "tinygo build ./..."
workdir = "explorer"
watch = ["**/*.go", "go.mod"]

Running spin watch will build the application, start it, then watch for changes in the files from the configuration or spin.toml itself, then restart the cycle when changes are detected:

$ spin watch
Executing the build command for component golang-explorer: tinygo build
Working directory: "explorer"
Successfully ran the build command for the Spin components.
Logging component stdio to ".spin/logs/"
Storing key-value data to ".spin/sqlite_key_value.db"

Serving http://127.0.0.1:3000
Available Routes:
...

For applications with static assets, there are options to skip building and only restart your applications — for example, the command used to continuously preview this blog post while being written was spin watch --skip-build.

New backend for the Spin Key Value Store

Spin comes with a built-in key/value store, available to every application through the SDKs with minimal configuration. In Spin 1.1, you can specify a Redis instance as the backend for the default key/value store. This way, you can continue to use the built-in key/value store that uses a local SQLite database at development time, and switch to a Redis instance in production, all without changing or recompiling your application!

Let’s have a look at a simple example written in Go with two API endpoints — one to create a key/value pair, one to retrieve it based on the key.

// Handle the POST request on the `/api/kv` path.
// For a complete example that contains error handling, see this repository
// https://github.com/radu-matei/spin-kv-explorer/blob/main/explorer/main.go
router.POST("/api/kv", func(w http.ResponseWriter, r *http.Request, _ spin.Params) {
    var input Pair
    if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    // open the default store
    store, err := kv.Open("default")

    // set the pair in key/value store, with the value as a byte array
    err = kv.Set(store, input.Key, []byte(input.Value))
 })

// Handle the GET request on the path `/api/kv/:key`
router.GET("/api/kv/:key", func(w http.ResponseWriter, r *http.Request, p spin.Params) {
    // open the default store
    store, err := kv.Open("default")

    // get the `key` path parameter from the request
    key := p.ByName("key")
    value, err := kv.Get(store, key)

    // form the pair, encode it to JSON, then return the response
    res := Pair{Key: key, Value: string(value)}
    json.NewEncoder(w).Encode(res)
})

In spin.toml, we need to enable the “default” key/value store for our component:

[[component]]
id = "hello-kv"
key_value_stores = ["default"]

After we spin build to compile the application, we can start the application with no additional configuration, and the default store will persist state in a local SQLite database file:

# start Spin with the default configuration
$ spin up
# notice the SQLite database file used for the default store.
Storing default key-value data to ".spin/sqlite_key_value.db"

Serving http://127.0.0.1:3000
Available Routes:
  kv: http://127.0.0.1:3000 (wildcard)

# set a key/value pair
$ curl -i  -X POST localhost:3000/api/kv -d '{"key": "foo", "value": "bar"}'
HTTP/1.1 200 OK

# get a key/value pair
$ curl -i  -X GET localhost:3000/api/kv/foo
HTTP/1.1 200 OK
content-length: 28

{"key":"foo","value":"bar"}

A SQLite database file is extremely convenient for a local development environment, or for simple cache scenarios, but if we want to persist data across applications that could be scheduled and run on different nodes, we need a better story for persistence — and Spin 1.1 lets you configure your own Redis instance as the backing store for the key/value feature!

We add a runtime-config.toml file that contains the configuration for our default key/value store to now point to a Redis instance:

[key_value_store.default]
type = "redis"
url = "rediss://xxxxxx"

We can restart Spin without changing or recompiling our application, and Spin will now use the new configuration to store the key/value data:

# start Spin with the configuration file
$ spin up --runtime-config-file runtime-config.toml
# this is now using the remote Redis instance for persistence
Storing default key-value data rediss://xxxxxx

Serving http://127.0.0.1:3000
Available Routes:
  kv: http://127.0.0.1:3000 (wildcard)

$ curl -i -X POST localhost:3000/api/kv -d '{"key": "persistent-foo", "value": "persistent-bar"}'
HTTP/1.1 200 OK

We can now verify the data is stored in the desired Redis instance:

$ redis-cli --tls -u rediss://xxxxxx
> get persistent-foo
"persistent-bar"

We are really excited about being able to dynamically change the implementation of a host component without modifying or recompiling your application. This feature would not be possible without the design of WebAssembly and the component model, and we are looking forward to hearing your feedback on how to improve this pattern in the future.

Routers for the Rust and Go SDKs

One of the most popular scenarios with Spin is writing web applications and APIs. In the Spin v0.8 release, we introduced a router for the JavaScript and TypeScript SDKs, which simplified writing web APIs significantly. In this release, we are adding simple HTTP routing to the Rust and Go SDKs.

Let’s have a look at a Rust example — we create a new router, then define handlers for our desired paths:

// the new HTTP router
use spin_sdk::http::Router;

/// A Spin HTTP component that internally routes requests.
#[http_component]
fn handle_route(req: Request) -> Result<Response> {
    let mut router = Router::new();
    router.get("/hello/:name", hello);

    router.handle(req)
}

pub fn hello(_req: Request, params: Params) -> Result<Response> {
    // handle route parameters
    let planet = params.get("name").context("expected route parameter `name`")?;

    Ok(http::Response::builder()
        .status(http::StatusCode::OK)
        .body(Some(format!("Hello, {name}").into()))?)
}

Let’s look at an example using the Go SDK:

import (
 "fmt"
 "net/http"

 spin "github.com/fermyon/spin/sdk/go/http"
)

// The entry point to a Spin HTTP request using the Go SDK.
spin.Handle(func(w http.ResponseWriter, r *http.Request) {
    router := spin.NewRouter()
    // Handle the GET request on the path `/api/kv/:key`
    router.GET("/hello/:name", func(w http.ResponseWriter, r *http.Request, p spin.Params) {
        fmt.Fprintf(w, "Hello %s!", p.ByName("name"))
    })

    router.ServeHTTP(w, r)
})

We are continuously looking at improving the ergonomics of our SDKs — if you have thoughts, please check out this issue for the Rust SDK, or reach out on Discord.

Spin and the WebAssembly Component Model

The WebAssembly component model is a proposal to build on top of the core WebAssembly standard to define how to enable cross-language composition, in a virtualizable, statically-analyzable, capability-safe, language-agnostic interfaces world.

If you are interested in a deep dive into the world of the WebAssembly component model, make sure to watch Luke Wagner’s talk from WasmDay 2022.

At Fermyon, we have been long supporters and contributors to the component model, and we are excited that Spin 1.1 is able to natively execute WebAssembly components. Thanks to the incredible work in the WebAssembly community, existing WebAssembly modules can be transparently adapted to WebAssembly components, paving the way for exciting new scenarios that the component model will enable for Spin!

Other improvements

Among other improvements in Spin 1.1, there is now a Modsurfer check file, so you can investigate Spin applications using Modsurfer, a new header in HTTP requests containing the client address, the Rust SDK now has more detailed error messages and traces, and we are experimenting with a default HTTP client in the Go SDK that is compatible with the standard library.

Thank you!

We would like to thank everyone who tried Spin 1.0 and who gave feedback, and to thank the almost 60 contributors to the Spin project who dedicate their time and energy making Spin better!

We want to give a special shout-out to new contributors @bholley, @psarna, @nucliweb, @nilslice, and @rylev, and to everyone involved in the Wasmtime and component model projects!

If you are interested in Spin, Fermyon Cloud, or other Fermyon projects, join the chat in the Fermyon Discord server and follow us on Twitter @fermyontech and @spinframework!

Announcing Spin 1.1


🔥 Recommended Posts


Quickstart Your Serveless Apps with Spin

Get Started