Using the Key Value Store

With Spin Key Value Store support in Fermyon Wasm Functions, you can persist non-relational data generated by your Spin application across application restarts and updates. Fermyon Wasm Functions will provision and manage the key value store on your behalf, handling the heavy lifting for you. Spin Key Value Stores are isolated to a single application, and components cannot access the store unless explicitly granted permission. This capabilities-based security model ensures that only authorized components can interact with the data. To learn more about the Spin Key Value Store SDK, please visit the API guide.

As part of this tutorial, we will build a Spin application which leverages the Key Value Store provided by Fermyon Wasm Functions for persistence. Once we have finished implementing the Spin app, we will run it locally using spin up for testing purposes, before we deploy it to Fermyon Wasm Functions using the spin aka deploy command.

About the Fermyon Wasm Functions Key Value Store

Fermyon Wasm Functions provides key value data storage that is low-latency, persisted, and globally replicated. A replica of the key value store exists in the same geographic area of each Fermyon Wasm Functions compute region, enabling efficient reads and writes. All standard key value operations exhibit read-your-writes behavior within a request.

Fermyon Wasm Functions globally distributed key value store

Key Value Limitations and Considerations

  • WASI KV atomic increment and compare-and-swap operations will incur higher latencies. Consider using alternative key value operations if latency is a concern.
  • Key value stores are currently scoped to applications and cannot be shared between applications.
  • Key value query rates are limited to enable experimenting with this feature. Rates can be increased to meet production needs per customer request.

Creating a New Spin Application

First, let’s start by creating a new Spin application using the http-js template:

$ spin new -t http-js --accept-defaults hello-key-value-store

$ cd hello-key-value-store

$ npm install

Grant Key Value Store Permission

To enable a component in Spin to use a key value store, you need to grant it the necessary permissions in the application’s manifest (spin.toml) by adding a key-value store label. This both grants the component access to the key value store and signals Fermyon Wasm Functions (FWF) to provision one for the application. In this tutorial, we’ll use the label “default”, but you can choose any label that works best for you.

[component.hello-key-value-store]
key_value_stores = [ "default" ]

When running the Spin application on your local machine for testing purposes, a new SQLite file (sqlite_key_value.db) is created in your application’s .spin folder. The database persists across Spin application invocations and updates.

Implementing the Spin Application

The http-js template generates a simple Hello World example in src/index.js. As part of this section, we’ll replace the existing code and use the HTTP router, provided by the template, to make our Spin application respond to the following HTTP request patterns:

  • GET /get/:key for retrieving a value from Key Value Store using the key provided as :key
  • POST /set/:key for storing a value provided as request payload using :key in Key Value Store

Let’s start by bringing necessary capabilities from the @fermyon/spin-sdk package into scope and laying out the routes:

import { Kv } from '@fermyon/spin-sdk';
import { AutoRouter } from 'itty-router'

const router = AutoRouter();
const decoder = new TextDecoder();

// register the GET endpoint
// pass the response object (res) and the route parameter :key to the handleGetValue function
router.get("/get/:key", ({key}) => handleGetValue(key));

// register the POST endpoint
// pass the response object (res), the router parameter :key and the request body to the handleSetValue function
router.post("/set/:key", async (req) => handleSetValue(req.params.key, await req.arrayBuffer()));

// handle incoming requests using the HTTP router
addEventListener('fetch', async (event) => {
    event.respondWith(router.fetch(event.request));
});

Incoming GET requests at /get/:key will be handled by the handleGetValue function. As part of the function, we use the Key Value Store APIs provided by the Spin SDK for JavaScript (import Kv from '@fermyon/spin-sdk'):

function handleGetValue(key) {
    // open the Key Value store with label "default"
    // if you specified a different label use the Kv.open("mylabel") function instead
    const store = Kv.openDefault();

    // check if key exists, if not return early with an HTTP 404 
    if (!store.exists(key)) {
        return new Response(null, { status: 404 });
    }

    // load JSON data at key from Key Value Store
    let found = store.getJson(key);

    // Return an HTTP 200 with corresponding HTTP header and payload
    // if something was found at position key in the Key Value Store 
    return new Response(
      JSON.stringify(found),
      { status: 200, headers: { "content-type": "application/json" } }
      );
}

Incoming POST requests at /set/:key will be handled by the handleSetValue function. After the request payload is validated, we use the Key Value Store APIs for persisting the data.

function handleSetValue(key, requestBody) {
    // parse the request body using JSON.parse
    let payload = JSON.parse(decoder.decode(requestBody));

    // check if the payload has expected properties
    // otherwise return HTTP 400
    if (!payload || !payload.firstName || !payload.lastName) {
        return new Response("Invalid payload received.\nExpecting {\"firstName\": \"some\", \"lastName\": \"some\"}", { status: 400 });
    }

    // open the Key Value store with label "default"
    // if you specified a different label use the Kv.open("mylabel") function instead 
    const store = Kv.openDefault();

    // store data in the Key Value store at key
    store.setJson(key, payload);

    // return an HTTP 200
    return new Response(null, { status: 200 });
}

Testing the Spin Application

You can run the Spin application on your local machine for testing purposes. Use the spin up --build command, to compile your source code to WebAssembly and run the application in a single step:

$ spin up --build
Building component hello-key-value-store with `npm run build`

> hello-key-value-store@1.0.0 build
> npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/hello-key-value-store.wasm

asset dist.js 12.2 KiB [emitted] [javascript module] (name: main)
orphan modules 26.4 KiB [orphan] 25 modules
runtime modules 396 bytes 2 modules
./src/spin.js + 6 modules 10.9 KiB [not cacheable] [built] [code generated]
webpack 5.97.1 compiled successfully in 78 ms
Using user provided wit in: combined-wit
Successfully written component
Finished building all Spin components
Logging component stdio to ".spin/logs/"
Storing default key-value data to ".spin/sqlite_key_value.db".

Serving http://127.0.0.1:3000
Available Routes:
  hello-key-value-store: http://127.0.0.1:3000 (wildcard)

From within a different terminal instance, we can use curl to interact with the Spin application. First, let’s send an HTTP POST request to localhost:3000/set/rs to persist data using the key rs:

$ curl -iX POST -H 'content-type: application/json' \
  -d '{"firstName": "River", "lastName": "Scott"}' \
  localhost:3000/set/rs
HTTP/1.1 200 OK
content-length: 0
date: Fri, 17 Jan 2025 12:33:35 GMT

Finally, let’s test receiving data by sending an HTTP GET request to localhost:3000/get/rs:

$ curl -iX GET localhost:3000/get/rs
HTTP/1.1 200 OK
content-type: application/json
content-length: 40
date: Fri, 17 Jan 2025 12:33:41 GMT

{"firstName":"River","lastName":"Scott"}

Move back to the first terminal instance and terminate your Spin application by pressing CTRL+C.

Deploying to Fermyon Wasm Functions

Fermyon Wasm Functions takes care of provisioning a Key Value Store for you. That said, all we have to do is deploying our Spin application using the spin aka deploy command:

$ spin aka deploy
App 'hello-key-value-store' initialized successfully.
Waiting for application to be ready...         ready
Application deployed to https://524468d8-104d-467d-ac32-24f0c1b0d54b.aka.fermyon.tech/
View application:   https://524468d8-104d-467d-ac32-24f0c1b0d54b.aka.fermyon.tech/

Once deployment has finished, we can - again - use curl to invoke the Spin application. This time we will send requests to the generated origin of our Spin application running on Fermyon Wasm Functions:

$ curl -iX POST -H 'content-type: application/json' \
  -d '{"firstName": "River", "lastName": "Scott"}' \
  https://524468d8-104d-467d-ac32-24f0c1b0d54b.aka.fermyon.tech/set/rs
HTTP/1.1 200 OK
Content-Length: 0
x-envoy-upstream-service-time: 30
Server: envoy
Date: Fri, 17 Jan 2025 12:42:40 GMT
Connection: keep-alive
Set-Cookie: akaalb_neutrino-alb=~op=neutrino:neutrino-eu|~rv=41~m=neutrino-eu:0|~os=695a3298b89ca0c21e87c492a8b63347~id=1614a121745bec33e8bceb4da4e7cb3d; path=/; HttpOnly; Secure; SameSite=None
Akamai-GRN: 0.c428d2bc.1737117760.1aa3356

Finally, let’s test receiving data by sending an HTTP GET request to localhost:3000/get/rs:

$ curl -i https://524468d8-104d-467d-ac32-24f0c1b0d54b.aka.fermyon.tech/get/rs
HTTP/1.1 200 OK
Content-Type: application/json
x-envoy-upstream-service-time: 402
Server: envoy
Date: Fri, 17 Jan 2025 12:42:49 GMT
Content-Length: 40
Connection: keep-alive
Set-Cookie: akaalb_neutrino-alb=~op=neutrino:neutrino-eu|~rv=84~m=neutrino-eu:0|~os=695a3298b89ca0c21e87c492a8b63347~id=e999136d42175b1ddf7ec9a8f4c463fa; path=/; HttpOnly; Secure; SameSite=None
Akamai-GRN: 0.9428d2bc.1737117768.3ca7f03

{"firstName":"River","lastName":"Scott"}

Congratulations 🎉, you now have an application and key value store running on Fermyon Wasm Functions.

Next Steps

  • Visit FAQ for frequently asked questions.
  • Check out the KV Explorer observability component that allows you to explore and manage the contents of your key value store