Use the Key Value store

Spin key value store support in Akamai Functions lets you persist non-relational data generated by your Spin application across application restarts and updates. Akamai Functions provisions and manages the key value store for you and handles the heavy lifting.

Spin key value stores are isolated to a single application. 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, go to the API guide.

As part of this tutorial, we'll build a Spin application that leverages the key value store provided by Akamai Functions for persistence. Once we've finished implementing the Spin app, we'll run it locally using spin up for testing purposes before we deploy it to Akamai Functions.

The Akamai Functions key value store capability is different and separate from EdgeKV. Edge is not currently compatible with Akamai Functions.

About the Akamai Functions key value store

Akamai 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 Akamai Functions compute region. This enables efficient reads and writes. All standard key value operations exhibit read-your-writes behavior within a request.

Akamai Functions provides key value data storage compute regions

Key value limitations and considerations

  • While the wasi:keyvalue/store and wasi:keyvalue/batch interfaces are supported, the wasi:keyvalue/atomic interface is not supported because it requires a consistency guarantee not provided by our global store.
  • 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.

Create a new Spin application

  1. Use the http-js template to create a new Spin application.
spin new -t http-js --accept-defaults hello-key-value-store

cd hello-key-value-store

npm install @spinframework/spin-kv 
📘

The above npm install command installs the default packages in the template as well as the @spinframework/spin-kv required for this tutorial.

Grant key value store permission

To enable the Spin component to use a key-value store, you need to grant it the necessary permissions in the application’s manifest (spin.toml).

  1. Add the label of the store to the component’s key_value_stores in spin.toml.

Akamai Functions, only allows the "default" label. It signals the platform to automatically provision a key value store for your Spin application.

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

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

Implement the Spin application

The http-js template generates a simple Hello World example in src/index.js. 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 To retrieve a value from key value store using the key provided as :key.
  • POST /set/:key To store a value provided as a request payload using :key in key value store.
  1. Bring in the necessary capabilities from the @spinframework/spin-kv package into scope and lay out the routes.
import { openDefault } from '@spinframework/spin-kv';  
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));  
});
  1. 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 (@spinframework/spin-kv).
function handleGetValue(key) {
    // open the Key Value store with label "default"
    const store = 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" } }
      );
}
  1. 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 = openDefault();

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

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

Test the Spin application

You can run the Spin application on your local machine for testing purposes.

  1. 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)
  1. From a different terminal instance, use curl to interact with the Spin application. First, 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
  1. Send an HTTP GET request to localhost:3000/get/rsto see if the data is received.
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"}
  1. You can move back to the first terminal instance and terminate your Spin application by pressing CTRL+C.

Deploy to Akamai Functions

Akamai Functions provisions the key value store for you. All you have to do is deploy the Spin application.

  1. Run the spin aka deploy command.
spin aka deploy
Name of new app: hello-key-value-store
Creating new app hello-key-value-store in account your-account
Note: If you would instead like to deploy to an existing app, cancel this deploy and link this workspace to the app with `spin aka app link`
OK to continue? yes
Workspace linked to app hello-key-value-store
Waiting for app to be ready... ready

App Routes:
- hello-key-value-store: https://ec8a19d8-6d10-4056-bb69-cc864306b489.aka.fermyon.tech (wildcard)
  1. Now that the deployment is complete, use this curl command to send requests to the generated origin of our Spin application running on Akamai 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
  1. Send an HTTP GET request to localhost:3000/get/rs to make sure that the data can be received.
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 Akamai Functions.