Using the Key Value Store
- About the Fermyon Wasm Functions Key Value Store
- Creating a New Spin Application
- Grant Key Value Store Permission
- Implementing the Spin Application
- Testing the Spin Application
- Deploying to Fermyon Wasm Functions
- Next Steps
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.

Key Value Limitations and Considerations
- WASI KV atomic
increment
andcompare-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