<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jasmine Mae</title>
    <description>The latest articles on Forem by Jasmine Mae (@jasminempa).</description>
    <link>https://forem.com/jasminempa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2323806%2F964c22ae-42b4-43d9-bc1a-dab75b9a50e3.jpg</url>
      <title>Forem: Jasmine Mae</title>
      <link>https://forem.com/jasminempa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jasminempa"/>
    <language>en</language>
    <item>
      <title>A Bridge over Troubled Water: Sending Azure Alerts to Slack with Spin</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Mon, 22 Sep 2025 14:42:18 +0000</pubDate>
      <link>https://forem.com/fermyon/a-bridge-over-troubled-water-sending-azure-alerts-to-slack-with-spin-395h</link>
      <guid>https://forem.com/fermyon/a-bridge-over-troubled-water-sending-azure-alerts-to-slack-with-spin-395h</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Kate Goldenring&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Improving the visibility of infrastructure alerts is key to reducing time to response. This is why many operations teams integrate alerts with their team messaging platforms. At Fermyon, we are one of those teams. We pull alerts from all our cloud providers and services into Slack channels. Recently, I ran into a challenge while trying to bridge Azure service alerts into Slack. Azure and Slack expect different JSON formats for messages, which meant I couldn’t simply drop a Slack incoming webhook URL into the Azure Portal. I needed an application to bridge these JSON differences, so I created &lt;a href="https://github.com/kate-goldenring/azure-slack-bridge" rel="noopener noreferrer"&gt;Azure Slack Bridge&lt;/a&gt;, a lightweight, HTTP-triggered transform function. This blog will walk through how to deploy the Azure Slack Bridge Spin application and equip you to build your own service bridges using Spin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Spin as a Service Bridge
&lt;/h2&gt;

&lt;p&gt;Rather than learning a new Azure product, such as Azure Logic Apps, I chose to build a simple Spin application to act as middleware between Azure and Slack. Azure formats alert payloads using its &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-common-schema#sample-alert-payload" rel="noopener noreferrer"&gt;Alert Common Schema&lt;/a&gt;, while Slack expects JSON payloads with a single &lt;code&gt;text&lt;/code&gt; field. I needed a function to bridge these two formats.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Azure Monitor → Azure Slack Bridge (Spin App) → Slack
     │                        │                  │
   (Complex               (Parses &amp;amp;           (Simple
    JSON)                 Transforms)         Message)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another benefit of this approach is portability and extensibility. Because the bridge is just a Spin application, it isn’t tied to Azure-specific tooling. If you’re operating in a multi-cloud environment, you can easily repurpose or extend the same bridge application to handle alerts from Amazon Web Services, Google Cloud Platform, or other cloud services without having to learn the quirks of yet another managed integration product. This gives you a consistent, reusable way to transform alerts into a Slack-compatible message across cloud providers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloud Service  →  Slack Bridge (Spin App)  →  Slack
     │                      │                   │
   (JSON)               (Parses &amp;amp;            (Simple
                        Transforms)          Message)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Abstracting further, we arrive at a general model where Spin applications resolve JSON disparities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Origin Service → Bridge (Spin App) → Destination Service
     │                 │                      │
 (JSON A)           (Parses &amp;amp;             (JSON B)
                    Transforms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So regardless of whether you use Slack or Azure, this blog should give you a toolkit for using Spin applications to bridge your services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Slack Service Bridge Implementation
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/kate-goldenring/azure-slack-bridge" rel="noopener noreferrer"&gt;Azure Slack Bridge Spin application&lt;/a&gt; is a HTTP-triggered WebAssembly component written in Rust. Its HTTP handler transforms an Azure alert into a Slack message in just five steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[http_component]
fn handle_slack_bridge(req: Request) -&amp;gt; anyhow::Result&amp;lt;impl IntoResponse&amp;gt; {
    // 1) Get the Slack webhook URL from a dynamic application variable 
    let slack_webhook_url = variables::get("slack_webhook_url")?;

    // 2) Parse the origin service JSON message from the HTTP request body
    let azure_alert = match serde_json::from_slice::&amp;lt;AzureAlert&amp;gt;(req.body()) {
        Ok(alert) =&amp;gt; alert,
        Err(e) =&amp;gt; {
            eprintln!("Failed to parse Azure alert: {}", e);
            return Ok(Response::builder()
                .status(400)
                .body(format!("Invalid alert payload: {}", e))
                .build());
        }
    };

    // 3) Format the origin message for Slack
    let slack_text = format_alert_message(&amp;amp;azure_alert);

    // 4) Send the message to Slack
    let slack_message = SlackMessage { text: slack_text };

    let request = Request::builder()
        .method(Method::Post)
        .body(serde_json::to_vec(&amp;amp;slack_message)?)
        .header("Content-type", "application/json")
        .uri(slack_webhook_url)
        .build();

    // 5) Return the result back to the origin service
    let response: Response = spin_sdk::http::send(request).await?;
    if *response.status() != 200 {
        return Ok(Response::new(500, "Failed to send to Slack"));
    }
    Ok(Response::new(200, ""))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the Azure Slack Bridge Application to Monitor Cosmos DB
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a common alerting example from Azure Cosmos DB. Cosmos DB is Azure’s fully managed database and one of Spin’s supported &lt;a href="https://spinframework.dev/v3/dynamic-configuration#azure-cosmosdb-key-value-store-provider" rel="noopener noreferrer"&gt;Key Value Store providers&lt;/a&gt;. This means you can &lt;a href="https://learn.microsoft.com/en-us/azure/aks/deploy-spinkube" rel="noopener noreferrer"&gt;deploy SpinKube to Azure Kubernetes Service&lt;/a&gt; and enable applications to use key-value stores with a Cosmos DB backing. With this setup in place, you’d likely want to monitor your infrastructure to track performance and catch issues early.&lt;/p&gt;

&lt;p&gt;One common &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/create-alerts" rel="noopener noreferrer"&gt;Cosmos DB metric to monitor&lt;/a&gt; is a threshold of 429 HTTP status codes, which are emitted when requests are rate-limited. For example, you may want an alert when 100 or more requests are throttled. Let’s see how to bridge that alert to Slack with the Azure Slack Bridge Spin application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create an Incoming Slack Webhook
&lt;/h3&gt;

&lt;p&gt;Slack supports incoming webhooks to post messages from external services. After creating a webhook, you’ll receive a unique URL to send JSON payloads with simple text messages. Follow &lt;a href="https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; to create a Slack app and webhook. Copy the final URL – you’ll set this as the &lt;code&gt;slack_webhook_url&lt;/code&gt; variable in the Azure Slack Bridge Spin application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Build and Deploy the Azure Slack Bridge Application
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Testing the Application Locally&lt;/strong&gt;&lt;br&gt;
After &lt;a href="https://spinframework.dev/v3/install" rel="noopener noreferrer"&gt;installing Spin&lt;/a&gt; and &lt;a href="https://www.fermyon.com/blog/azure-slack-bridge-spin-app#step-1-create-an-incoming-slack-webhook" rel="noopener noreferrer"&gt;creating a Slack incoming Webhook&lt;/a&gt;, you can test your application locally. First, build and run the application, setting your Slack Webhook URL as a &lt;a href="https://spinframework.dev/v3/variables#application-variables" rel="noopener noreferrer"&gt;Spin Variable&lt;/a&gt; using the environment variable provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/kate-goldenring/azure-slack-bridge
cd azure-slack-bridge
SPIN_VARIABLE_SLACK_WEBHOOK_URL="https://hooks.slack.com/services/&amp;lt;...&amp;gt; spin build --up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, test your connection to Slack with the example Azure alert payload provided in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST \
  -H "Content-Type: application/json" \
  -d @sample-alert.json \
  localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a new message in the Slack channel you configured for the webhook. The message title should be: “⚠️ 🔴 WCUS-R2-Gen2”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying Your Spin Application&lt;/strong&gt;&lt;br&gt;
Spin applications can be deployed to any &lt;a href="https://spinframework.dev/v3/deploying" rel="noopener noreferrer"&gt;Spin-compatible platform&lt;/a&gt;. Many platforms make this process simple with Spin CLI plugins. The following are three options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy to &lt;a href="https://www.fermyon.com/blog/azure-slack-bridge-spin-app#:~:text=Deploy%20to-,Fermyon%20Wasm%20Functions,-%2C%20a%20multi%2Dtenant" rel="noopener noreferrer"&gt;Fermyon Wasm Functions&lt;/a&gt;, a multi-tenant, hosted, globally distributed engine for serverless functions running on Akamai Cloud, using the &lt;code&gt;aka&lt;/code&gt; plugin:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Currently, Fermyon Wasm Functions is in limited access, public preview, so you must first &lt;a href="https://fibsu0jcu2g.typeform.com/fwf-preview?typeform-source=localhost" rel="noopener noreferrer"&gt;fill out this form&lt;/a&gt; to request access to the service.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Deploy to &lt;a href="https://www.fermyon.com/cloud" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt;, a multi-tenant, hosted, functions service, using the &lt;code&gt;cloud&lt;/code&gt; plugin:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin cloud deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Your &lt;a href="https://www.spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt; cluster, using the the &lt;code&gt;kube&lt;/code&gt; plugin:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin registry push ttl.sh/azure-slack-bridge:24h
spin kube scaffold -f ttl.sh/azure-slack-bridge:24h | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After deploying your application, you should have a URL for your middleware Spin application that you can use when &lt;a href="https://www.fermyon.com/blog/azure-slack-bridge-spin-app#step-3-set-up-the-azure-alert-pipeline" rel="noopener noreferrer"&gt;setting up your Alert Group in the next step&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Set Up the Azure Alert Pipeline
&lt;/h3&gt;

&lt;p&gt;Follow this &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/create-alerts" rel="noopener noreferrer"&gt;Azure tutorial&lt;/a&gt; to create an Alert Rule for a threshold of 429 HTTP status codes from Cosmos DB. When creating the rule, link it to an Action Group. This is where you’ll create an Action Group with a Webhook action type. Paste the URL of the deployed application into the URI field of the webhook pane. Be sure to enable the common alert schema for the webhook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu37bbzb0vhpywu0deodu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu37bbzb0vhpywu0deodu.png" alt="Creating an Azure alert group that is connected to the Spin Azure Slack Bridge application" width="800" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Seeing Alerts in Slack
&lt;/h3&gt;

&lt;p&gt;Now, alerts will be bridged into Slack in clear formatting:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e5syb1itvek6smbb10v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5e5syb1itvek6smbb10v.png" alt="Example Slack message of a Cosmos DB alert for too many 429s" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Bridge over Troubled Water
&lt;/h2&gt;

&lt;p&gt;Bridging Azure alerts into Slack doesn’t need to be complicated. With Spin, you can build lightweight serverless applications that act as middleware between services, transforming JSON payloads, normalizing data formats, and routing events exactly where you need them. While this example focused on Cosmos DB alerts flowing into Slack, the same pattern applies to any Azure service or even other cloud providers.&lt;/p&gt;

&lt;p&gt;Try out the &lt;a href="https://github.com/kate-goldenring/azure-slack-bridge" rel="noopener noreferrer"&gt;Azure Slack Bridge&lt;/a&gt; to get started, and then experiment with building your own service bridges to streamline how your team stays informed and responsive.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>webassembly</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Introducing wasi-grpc for Spin</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Tue, 09 Sep 2025 02:09:09 +0000</pubDate>
      <link>https://forem.com/fermyon/introducing-wasi-grpc-for-spin-24kg</link>
      <guid>https://forem.com/fermyon/introducing-wasi-grpc-for-spin-24kg</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Brian Hardock&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modern microservice ecosystems often rely on gRPC for efficient, strongly typed communication. gRPC builds on top of HTTP/2, taking advantage of multiplexed streams and binary serialization (Protobuf) to deliver high throughput and low latency.&lt;/p&gt;

&lt;p&gt;Until &lt;a href="https://github.com/spinframework/spin/releases/tag/v3.4.1" rel="noopener noreferrer"&gt;Spin 3.4&lt;/a&gt;, Spin components could only make outbound HTTP/1.1 requests. This meant gRPC clients could not run inside Spin directly; developers had to rely on sidecars or proxy services to bridge the gap. That added extra complexity and slowed down applications.&lt;/p&gt;

&lt;p&gt;Spin 3.4 introduces outbound HTTP/2 support, enabling components to act as first-class gRPC clients. Spin applications can now call into existing gRPC-based systems, cloud APIs, and service meshes directly, without workarounds.&lt;/p&gt;

&lt;p&gt;In this blog post, we will walk through a tutorial showing how to build a Spin component in Rust that connects to a simple gRPC service using the new &lt;a href="https://github.com/fermyon/wasi-grpc" rel="noopener noreferrer"&gt;wasi-grpc&lt;/a&gt; crate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we will be adapting the &lt;a href="https://github.com/hyperium/tonic/tree/master" rel="noopener noreferrer"&gt;tonic&lt;/a&gt; &lt;code&gt;helloworld&lt;/code&gt; example &lt;a href="https://github.com/hyperium/tonic/blob/master/examples/src/helloworld/client.rs" rel="noopener noreferrer"&gt;client&lt;/a&gt; to execute in Spin. Additionally, for testing, we will run the &lt;code&gt;helloworld&lt;/code&gt; server to execute our component against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a new Spin app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin new -t http-rust helloworld-client --accept-defaults
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new helloworld-client directory with a Spin HTTP Rust application. Change into this directory to continue with the tutorial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd helloworld-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Setup
&lt;/h2&gt;

&lt;p&gt;First, in &lt;code&gt;Cargo.toml&lt;/code&gt;, add the dependencies for working with gRPC, Protobuf, and the wasi-grpc crate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[dependencies]
anyhow = "1"
prost = "0.13.5"
wasi-grpc = "0.1.0"
spin-sdk =  "4.0.0"
tonic = { version = "0.13.1", features = ["codegen", "prost", "router"], default-features = false}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, copy the &lt;a href="https://github.com/hyperium/tonic/blob/master/examples/proto/helloworld/helloworld.proto" rel="noopener noreferrer"&gt;helloworld proto&lt;/a&gt; into your current working directory.&lt;/p&gt;

&lt;p&gt;With that in place, we can add our build dependencies for generating code from our proto file. In your &lt;code&gt;Cargo.toml&lt;/code&gt; add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[build-dependencies]
tonic-build = { version = "0.13.1", features = ["prost"] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally we can add the &lt;code&gt;build.rs&lt;/code&gt; which will generate our gRPC client from the &lt;code&gt;helloworld&lt;/code&gt; proto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In build.rs

fn main() {
    tonic_build::configure()
        .build_transport(false)
        .compile_protos(&amp;amp;["helloworld.proto"], &amp;amp;[""])
        .unwrap();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let’s implement!
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;src/lib.rs&lt;/code&gt;, let’s start by pulling in the generated code from executing our &lt;code&gt;build.rs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;

pub mod hello_world {
    tonic::include_proto!("helloworld");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next let’s pull in the &lt;code&gt;wasi_grpc::WasiGrpcEndpoint&lt;/code&gt; type which implements &lt;code&gt;tower_service::Service&lt;/code&gt;. This type creates a bridge between tonic and &lt;a href="https://github.com/fermyon/wasi-hyperium" rel="noopener noreferrer"&gt;wasi-hyperium&lt;/a&gt; which is what &lt;code&gt;wasi-grpc&lt;/code&gt; uses under the hood for sending outbound HTTP requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use wasi_grpc::WasiGrpcEndpoint;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NOTE: It was decided to use &lt;code&gt;wasi-hyperium&lt;/code&gt; as the underlying mechanism for sending outbound requests because the &lt;code&gt;spin-sdk&lt;/code&gt; currently has certain limitations with respect to outbound streaming.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally we can implement our request handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[http_component]
async fn handler(_req: http::Request) -&amp;gt; anyhow::Result&amp;lt;impl IntoResponse&amp;gt; {
    let endpoint_uri = "http://[::1]:50051".parse().context("Failed to parse endpoint URI")?;
    let endpoint = WasiGrpcEndpoint::new(endpoint_uri);
    let mut client = GreeterClient::new(endpoint);

    let request = Request::new(HelloRequest {
        name: "World".to_string(),
    });

    let message = client
        .say_hello(request)
        .await?
        .into_inner()
        .message;

    let response = http::Response::new(200, message);

    Ok(response)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NOTE: For simplicity, we are using a hardcoded endpoint (“http://[::1]:50051”) to communicate with our local gRPC server that implements the &lt;code&gt;GreeterService&lt;/code&gt;. For production applications it is recommended to use &lt;a href="https://spinframework.dev/v3/variables" rel="noopener noreferrer"&gt;application variables&lt;/a&gt; to inject in the value of the address at run/deploy time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Putting this all together, the final code in &lt;code&gt;src/lib.rs&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use anyhow::Context;
use spin_sdk::http::{self, IntoResponse};
use spin_sdk::http_component;
use tonic::Request;
use wasi_grpc::WasiGrpcEndpoint;

use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;

pub mod hello_world {
    tonic::include_proto!("helloworld");
}

// Spin HTTP component
#[http_component]
async fn handler(_req: http::Request) -&amp;gt; anyhow::Result&amp;lt;impl IntoResponse&amp;gt; {
    let endpoint_uri = "http://[::1]:50051".parse().context("Failed to parse endpoint URI")?;
    let endpoint = WasiGrpcEndpoint::new(endpoint_uri);
    let mut client = GreeterClient::new(endpoint);

    let request = Request::new(HelloRequest {
        name: "World".to_string(),
    });

    let message = client
        .say_hello(request)
        .await?
        .into_inner()
        .message;

    // Return gRPC response as HTTP response body for the Spin component
    let response = http::Response::new(200, message);

    Ok(response)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let’s take it for a Spin!
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, we will be using the &lt;a href="https://github.com/hyperium/tonic/blob/master/examples/src/helloworld/server.rs" rel="noopener noreferrer"&gt;helloworld&lt;/a&gt; server to execute our app against. The easiest way to run this would be to clone the &lt;a href="https://github.com/hyperium/tonic/tree/master" rel="noopener noreferrer"&gt;tonic&lt;/a&gt; repository and run the helloworld server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gh repo clone hyperium/tonic
$ cargo run --bin helloworld-server
...
GreeterServer listening on [::1]:50051
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will want to configure our Spin app so that our component can communicate with the our server running at &lt;code&gt;[::1]:50051&lt;/code&gt;. In your &lt;code&gt;spin.toml&lt;/code&gt;, you’ll want to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[component.helloworld-client]
...
allowed_outbound_hosts = ["http://[::1]:50051"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our server running and app properly configured, let’s &lt;code&gt;spin up&lt;/code&gt; our application and send it a request to execute our new component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE=[::1]:50051 spin up --build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NOTE: The &lt;code&gt;SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE&lt;/code&gt; environment variable is to tell the Spin runtime that we are intending to use HTTP/2 without TLS. If you’re running this example against a server with TLS enabled you can omit this variable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally, let’s send a request to our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:3000/
Hello World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Spin 3.4’s outbound HTTP/2 support removes one of the biggest barriers to using Spin in modern service-oriented systems: direct gRPC client support. With the help of the wasi-grpc crate, components can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call gRPC microservices directly without sidecars&lt;/li&gt;
&lt;li&gt;Integrate with APIs built on gRPC&lt;/li&gt;
&lt;li&gt;Leverage high-performance streaming patterns as first-class citizens in Spin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example above uses the simple helloworld service, but the same approach applies to more advanced services — including streaming APIs. For inspiration, check out the &lt;a href="https://github.com/fermyon/wasi-grpc/tree/main/examples/routeguide-client" rel="noopener noreferrer"&gt;routeguide-client&lt;/a&gt; in the &lt;a href="https://github.com/fermyon/wasi-grpc" rel="noopener noreferrer"&gt;wasi-grpc&lt;/a&gt; repository.&lt;/p&gt;

&lt;p&gt;If you have any questions, we’d love to help over in the &lt;a href="https://cloud-native.slack.com/archives/C089NJ9G1V0" rel="noopener noreferrer"&gt;Spin CNCF Slack channel&lt;/a&gt;. Hope to see you there!&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>OpenAPI Documentation for Spin Apps with Rust</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Thu, 28 Aug 2025 16:18:40 +0000</pubDate>
      <link>https://forem.com/fermyon/openapi-documentation-for-spin-apps-with-rust-c74</link>
      <guid>https://forem.com/fermyon/openapi-documentation-for-spin-apps-with-rust-c74</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Thorsten Hans&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generating and exposing machine- and human-readable documentation for RESTful APIs is a must, and those built with &lt;a href="https://github.com/spinframework/spin" rel="noopener noreferrer"&gt;Spin&lt;/a&gt; are no exception. The &lt;a href="https://spec.openapis.org/" rel="noopener noreferrer"&gt;OpenAPI Specification (OAS)&lt;/a&gt; is the de-facto standard when it comes to documenting RESTful APIs. In this article we will explore how one could generate OAS-compliant documentation from within Spin applications written in Rust.&lt;/p&gt;

&lt;p&gt;Before diving straight into a practical example, we’ll do a quick refresher on what OAS actually is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the OpenAPI Specification (OAS)
&lt;/h2&gt;

&lt;p&gt;The OpenAPI Specification (OAS) serves as a universal, language-agnostic standard for describing the structure and features of RESTful APIs. By establishing a clear, machine- and human-readable contract, OAS allows developers, API consumers, and tools to understand available endpoints, request and response schemas, authentication methods, and more.&lt;/p&gt;

&lt;p&gt;Leveraging the OAS description, teams can also provide interactive documentation UIs, generate client SDKs for various programming languages, server mocks for rapid prototyping, and even automated tests for end-to-end validation.&lt;/p&gt;

&lt;p&gt;The OpenAPI description not only streamlines onboarding for new developers but also ensures that your API remains consistently documented and discoverable throughout its lifecycle, making OAS an indispensable tool for modern API development.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Guide To OpenAPI Documentation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites and Setup
&lt;/h3&gt;

&lt;p&gt;To follow along with the instructions and snippets shown as part of this article, you must have the following things installed on your development machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spin CLI version &lt;code&gt;3.3.1&lt;/code&gt; or later (see &lt;a href="https://spinframework.dev/v3/install" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Rust version &lt;code&gt;1.86.0&lt;/code&gt; or later including the &lt;code&gt;wasm32-wasip1&lt;/code&gt; target (see &lt;a href="https://www.rust-lang.org/tools/install" rel="noopener noreferrer"&gt;Rust&lt;/a&gt; and &lt;a href="https://spinframework.dev/v3/rust-components#install-the-tools" rel="noopener noreferrer"&gt;target installation instructions&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Git CLI (see &lt;a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;An Editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will use an API that implements simple “to-do” list functionality as a starting point. You can find the source code on GitHub at &lt;a href="https://github.com/ThorstenHans/spin-todo-api" rel="noopener noreferrer"&gt;https://github.com/ThorstenHans/spin-todo-api&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clone the repository and build the Spin application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cs"&gt;# Clone the Spin application from GitHub&lt;/span&gt;
&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="py"&gt;.com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ThorstenHans&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="py"&gt;.git&lt;/span&gt;

&lt;span class="cs"&gt;# Move into the application directory&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;

&lt;span class="cs"&gt;# Build the Spin application&lt;/span&gt;
&lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your development machine has all the necessary tools and is set up correctly, you should see output along the lines of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;Building&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt; &lt;span class="n"&gt;spin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;todo&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;cargo&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="n"&gt;wasm32&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wasip1&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;

&lt;span class="cs"&gt;# ...&lt;/span&gt;
&lt;span class="cs"&gt;# ...&lt;/span&gt;

&lt;span class="n"&gt;Finished&lt;/span&gt; &lt;span class="n"&gt;building&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;Spin&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exploring the ToDo-API
&lt;/h3&gt;

&lt;p&gt;Now that we’ve successfully built our application, let’s take a look at it’s functionality. The pre-coded ToDo-API is a RESTful HTTP API that exposes the following endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/todos&lt;/code&gt; - Retrieve a list of all ToDo-items&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/todos/:id&lt;/code&gt; - Retrieve a particular ToDo-item using its identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/todos&lt;/code&gt; - Create a new ToDo-item&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/todos/:id/toggle&lt;/code&gt; - Toggle a ToDo-item using its identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /api/todos/:id&lt;/code&gt; - Delete a ToDo-item using its identifier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ToDo-items are stored and retrieved from a key-value store (see &lt;code&gt;src/domain/todo.rs&lt;/code&gt;) which is automatically managed by the runtime (Spin, Fermyon Wasm Functions or Fermyon Cloud) on your behalf.&lt;/p&gt;

&lt;p&gt;On top of the default dependencies, the following crates have been added to the ToDo-API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;uuid (features: v4, serde)&lt;/code&gt; - Used to generate identifiers for ToDo-items&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serde (features: derive)&lt;/code&gt; - Used to serialize and deserialize Rust structs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;serde_json&lt;/code&gt; - JSON support for &lt;code&gt;serde&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;url&lt;/code&gt; - Which we’ll use later to parse URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Meet the &lt;code&gt;utoipa&lt;/code&gt; Crate
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;utoipa&lt;/code&gt; &lt;a href="https://crates.io/crates/utoipa" rel="noopener noreferrer"&gt;crate&lt;/a&gt; is designed to simplify the process of generating OpenAPI documentation directly from within your codebase. By leveraging macros, &lt;code&gt;utoipa&lt;/code&gt; automatically produces a comprehensive OAS description according to the OAS specification.&lt;/p&gt;

&lt;p&gt;This allows us to keep the documentation of our RESTful API in sync with the actual implementation, while requiring minimal effort.&lt;/p&gt;

&lt;p&gt;To add &lt;code&gt;utoipa&lt;/code&gt; with its optional &lt;code&gt;uuid&lt;/code&gt; feature as a dependency use the &lt;code&gt;cargo add&lt;/code&gt; command as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cs"&gt;# Run the following command from the spin-todo-api directory&lt;/span&gt;

&lt;span class="cs"&gt;# Add utoipa with uuid feature as a dependency&lt;/span&gt;
&lt;span class="n"&gt;cargo&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt; &lt;span class="n"&gt;utoipa&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;utoipa&lt;/code&gt; crate being added as a dependency to the ToDo-API (see &lt;code&gt;Cargo.toml&lt;/code&gt;), we can now start documenting our API. Generally speaking, there are three different documentation assets we have to provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;General API documentation&lt;/li&gt;
&lt;li&gt;API request- and response-model documentation&lt;/li&gt;
&lt;li&gt;API endpoint documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following sections will guide you through each documentation asset, and provide an example of how to update the implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  General API Documentation
&lt;/h3&gt;

&lt;p&gt;You can think of “General API documentation” as fundamental metadata about your RESTful API (such as contact information, license, tags, etc.). We use the &lt;code&gt;#[openapi]&lt;/code&gt; macro on a custom Rust struct to specify all static information.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;utoipa&lt;/code&gt; also supports customizing the “General API documentation” at runtime by either &lt;a href="https://docs.rs/utoipa/5.4.0/utoipa/trait.Modify.html" rel="noopener noreferrer"&gt;adding custom modifiers&lt;/a&gt; (for reusable, modular customizations) or by &lt;a href="https://docs.rs/utoipa/5.4.0/utoipa/#modify-openapi-at-runtime" rel="noopener noreferrer"&gt;leveraging the APIs provided by&lt;/a&gt; &lt;code&gt;utoipa&lt;/code&gt; (for simpler, direct modifications).&lt;/p&gt;

&lt;p&gt;We will start with defining all static information using the &lt;code&gt;#[openapi]&lt;/code&gt; macro, but will add dynamic customization later in this article. First, we have to create a new &lt;code&gt;struct&lt;/code&gt; in &lt;code&gt;src/handlers/docs.rs&lt;/code&gt;, which we will decorate using the &lt;code&gt;utoipa::openapi&lt;/code&gt; macro. Additionally, we’ll derive from &lt;code&gt;utoipa::OpenApi&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;utoipa&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;OpenApi&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(OpenApi)]&lt;/span&gt;
&lt;span class="nd"&gt;#[openapi(&lt;/span&gt;
  &lt;span class="nd"&gt;info(&lt;/span&gt;
    &lt;span class="nd"&gt;title&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ToDo API"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"An awesome ToDo API"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;terms_of_service&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;include_str&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nd"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"../../fake_terms_of_service.txt"&lt;/span&gt;&lt;span class="nd"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;contact(&lt;/span&gt;
      &lt;span class="nd"&gt;name&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Fermyon Engineering"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; 
      &lt;span class="nd"&gt;email&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"engineering@fermyon.com"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; 
      &lt;span class="nd"&gt;url&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://www.fermyon.com"&lt;/span&gt;
    &lt;span class="nd"&gt;)&lt;/span&gt;
  &lt;span class="nd"&gt;),&lt;/span&gt;
  &lt;span class="nd"&gt;tags(&lt;/span&gt;
    &lt;span class="nd"&gt;(name&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"todos"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ToDo API Endpoints"&lt;/span&gt;&lt;span class="nd"&gt;)&lt;/span&gt;
  &lt;span class="nd"&gt;),&lt;/span&gt;
  &lt;span class="nd"&gt;paths(&lt;/span&gt;
    &lt;span class="nd"&gt;crate::handlers::todo::get_by_id,&lt;/span&gt;
  &lt;span class="nd"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;OpenApiDocs&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API Request and Response Model Documentation
&lt;/h2&gt;

&lt;p&gt;The ToDo-API provides dedicated request and response models to encapsulate the API from the underlying data structures used for persistence. By using dedicated API models, you can design clean and efficient API contracts and provide fine-grained validation logic.&lt;/p&gt;

&lt;p&gt;Documenting your API models helps humans and computers to understand their contextual meaning. With &lt;code&gt;utoipa&lt;/code&gt;, documenting these models is done by using the &lt;code&gt;ToSchema&lt;/code&gt; derive macro. The &lt;code&gt;ToSchema&lt;/code&gt; derive macro integrates seamlessly with Rust doc comments for specifying the title of a particular model and its fields. Also, rename instructions from &lt;code&gt;serde&lt;/code&gt; are taken into context as well.&lt;/p&gt;

&lt;p&gt;You can use the &lt;code&gt;#[schema]&lt;/code&gt; macro to provide samples and &lt;a href="https://docs.rs/utoipa/latest/utoipa/derive.ToSchema.html#named-field-struct-optional-configuration-options-for-schema" rel="noopener noreferrer"&gt;perform further customizations&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;utoipa&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ToSchema&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/// ToDo-item&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Serialize,&lt;/span&gt; &lt;span class="nd"&gt;ToSchema)]&lt;/span&gt;
&lt;span class="nd"&gt;#[serde(rename_all&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"camelCase"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="nd"&gt;#[schema(example&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;json&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nd"&gt;(&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"059c7906-ce72-4433-94df-441beb14d96a"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"contents"&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Buy Milk"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"isCompleted"&lt;/span&gt;&lt;span class="nd"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="nd"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ToDoModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cd"&gt;/// Unique identifier&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="cd"&gt;/// ToDo contents&lt;/span&gt;
  &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="cd"&gt;/// Is Completed?&lt;/span&gt;
  &lt;span class="n"&gt;is_completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  API Endpoint Documentation
&lt;/h3&gt;

&lt;p&gt;With the “General API documentation” and a response model documentation in place, we can move on and take a look at an endpoint documentation. For illustration purposes, we’ll document the &lt;code&gt;GET /api/todos/:id&lt;/code&gt; endpoint. To do so, we must decorate the corresponding handler &lt;code&gt;get_by_id&lt;/code&gt; (located in &lt;code&gt;src/handlers/todo.rs&lt;/code&gt;) with the &lt;code&gt;#[utoipa::path]&lt;/code&gt; attribute macro.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;#[utoipa::path]&lt;/code&gt; macro has an extensive API, allowing you to tailor all aspects of the endpoint-specific documentation. Take a minute to &lt;a href="https://docs.rs/utoipa/5.4.0/utoipa/attr.path.html" rel="noopener noreferrer"&gt;familiarize yourself with the macro and its capabilities&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[utoipa::path(&lt;/span&gt;
    &lt;span class="nd"&gt;get,&lt;/span&gt;
  &lt;span class="nd"&gt;path&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/api/todos/{id}"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt;
  &lt;span class="nd"&gt;tags&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"todos"&lt;/span&gt;&lt;span class="nd"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Retrieve a ToDo-item using its identifier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ToDo identifier"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Desired ToDo-item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ToDoModel&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bad Request"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Desired ToDo-item was not found"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Internal Server Error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_by_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that in contrast to the Spin HTTP router, &lt;code&gt;utoipa&lt;/code&gt; expects route parameters to be specified using curly braces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Exposing The OpenAPI Description
&lt;/h3&gt;

&lt;p&gt;To expose the OpenAPI description, we first have to add another &lt;code&gt;GET&lt;/code&gt; endpoint to the Spin application. To do so, use &lt;code&gt;router.get&lt;/code&gt; in the entry point function (located in &lt;code&gt;src/lib.rs&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;
&lt;span class="nd"&gt;#[http_component]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_spin_todo_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs/openapi-description.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get_openapi_description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//...&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have to add the actual handler in &lt;code&gt;src/handlers/docs.rs&lt;/code&gt; and use our custom &lt;code&gt;OpenApi&lt;/code&gt; struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;spin_sdk&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;http&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_openapi_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;openapi_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OpenApiDocs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openapi_description&lt;/span&gt;&lt;span class="nf"&gt;.to_pretty_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the handler in place, let’s compile Spin application and test the new endpoint on our local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="cs"&gt;# Compile the Spin application&lt;/span&gt;
&lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="cs"&gt;# Run the Spin application&lt;/span&gt;
&lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From within a new terminal instance, use a tool like &lt;code&gt;curl&lt;/code&gt; and send a &lt;code&gt;GET&lt;/code&gt; request to the new endpoint at &lt;code&gt;http://localhost:3000/docs/openapi-description.json&lt;/code&gt;. You should see the Spin application responding with the prettified OpenAPI Description as JSON.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The OpenAPI Description could also be exposed as YAML file. To do so, use the optional &lt;code&gt;yaml&lt;/code&gt; feature from the &lt;code&gt;utoipa&lt;/code&gt; crate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Adding OpenAPI Documentation UI
&lt;/h3&gt;

&lt;p&gt;With the machine-readable OpenAPI Description in place, we want to go one step further and make our Spin application serve an OpenAPI Documentation UI. With &lt;code&gt;utoipa&lt;/code&gt;, you can choose from different OpenAPI Documentation UIs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://swagger.io/tools/swagger-ui/" rel="noopener noreferrer"&gt;Swagger UI&lt;/a&gt; using the &lt;code&gt;utoipa-swagger-ui&lt;/code&gt; crate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redocly.com/" rel="noopener noreferrer"&gt;Redoc&lt;/a&gt; using the &lt;code&gt;utoipa-redoc&lt;/code&gt; crate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rapidocweb.com/" rel="noopener noreferrer"&gt;RapiDoc&lt;/a&gt; using the &lt;code&gt;utoipa-rapidoc&lt;/code&gt; crate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow these steps to add good old Swagger UI to the Spin application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;utoipa-swagger-ui&lt;/code&gt; as a dependency (&lt;code&gt;cargo add utoipa-swagger-ui&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Register a new route after the OpenAPI Description route:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[http_component]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_spin_todo_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="c1"&gt;// ...&lt;/span&gt;
   &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs/openapi-description.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;get_openapi_description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;render_openapi_docs_ui&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="nf"&gt;.handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add a new handler to src/handlers/docs.rs which will examine the request path and render the Swagger UI for all requests not targeting the OpenAPI Description:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;render_openapi_docs_ui&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
       &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-path-info"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-path-info is not present"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/docs/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;utoipa_swagger_ui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"openapi-description.json"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;utoipa_swagger_ui&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;swagger_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;swagger_file&lt;/span&gt;
            &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;ResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="py"&gt;.content_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="py"&gt;.bytes&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                    &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="nf"&gt;.unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Not Found"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Internal Server Error"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile the Spin application and run it again on your development machine(&lt;code&gt;spin build &amp;amp;&amp;amp; spin up&lt;/code&gt;). Once compilation has finished and Spin CLI has spawned the listener on port &lt;code&gt;3000&lt;/code&gt;, browse &lt;code&gt;http://localhost:3000/docs/&lt;/code&gt; which should render Swagger UI and have the OpenAPI Description already loaded for you (as shown in the following figure):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqpy66q8en31hwwaeeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefqpy66q8en31hwwaeeu.png" alt="Swagger UI generated by utoipa" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Customizing OpenAPI Description
&lt;/h3&gt;

&lt;p&gt;Although we provided plenty of information about the ToDo-API, we now want to go one step further and customize the OpenAPI Description based on incoming request circumstances. &lt;/p&gt;

&lt;p&gt;We are going to extend the OpenAPI Description by adding a different Server depending on the host that is serving the documentation. This allows us to have a &lt;strong&gt;Development Server&lt;/strong&gt; specified in the OpenAPI Description if we run the application on our local machine using &lt;code&gt;spin up&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if we deploy the application to a remote runtime - such as Fermyon Wasm Functions - we want the &lt;strong&gt;Production Server&lt;/strong&gt; to be specified in the OpenAPI Description document.&lt;/p&gt;

&lt;p&gt;This simple example illustrates how you could customize the OpenAPI Description document before serving it over HTTP.&lt;/p&gt;

&lt;p&gt;Customizations in &lt;code&gt;utoipa&lt;/code&gt; are either achieved by adding custom Modifiers (custom structs that implement the &lt;code&gt;Modify&lt;/code&gt; trait) or by mutating our instance of &lt;code&gt;OpenApiDocs&lt;/code&gt; before sending it as an HTTP response body.&lt;/p&gt;

&lt;p&gt;We’ll use the latter approach for adding a different server based on incoming request headers. To achieve this, update the &lt;code&gt;get_openapi_description&lt;/code&gt; handler (in &lt;code&gt;src/handlers/docs.rs&lt;/code&gt;) as shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;utoipa&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ServerBuilder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_openapi_description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;IntoResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;openapi_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OpenApiDocs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_server_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;openapi_description&lt;/span&gt;&lt;span class="py"&gt;.servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nn"&gt;ServerBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ResponseBuilder&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openapi_description&lt;/span&gt;&lt;span class="nf"&gt;.to_pretty_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_server_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nf"&gt;is_local_spin_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;full_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
                &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url should be set when running api with spin CLI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url shall not be empty when running api with spin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url should be a valid url"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"{}://{}/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="nf"&gt;.scheme&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                    &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"{}:{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="nf"&gt;.host_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url should have host"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="nf"&gt;.port&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-full-url should have port"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Local Development Server"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;
                &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-forwarded-host"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-forwarded-host should be set via FWF"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-forwarded-host shall not be empty on FWF"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://{host}/"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Production Server"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_local_spin_runtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spin-client-addr"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_some&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the customization in place, you can build and run the Spin application again. When browsing the Swagger UI again, you’ll recognize the &lt;strong&gt;Development Server&lt;/strong&gt; being displayed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw3r8o210tcr7ffx4dq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpw3r8o210tcr7ffx4dq8.png" alt="Customized Swagger UI generated by utoipa" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By integrating the &lt;code&gt;utoipa&lt;/code&gt; crate into Spin applications, we can keep our API implementation and documentation in perfect sync while reducing manual overhead. What starts with a handful of macros quickly results in a fully navigable OpenAPI Description, complete with Swagger UI, and even adaptable to runtime conditions. Whether you’re building for local development or deploying at scale, this approach ensures your APIs remain transparent, discoverable, and ready for both humans and machines to consume. Instead of treating documentation as an afterthought, you embed it directly into the lifecycle of your service. In other words, your documentation becomes as alive as your code—always current, always reliable, and always serving as the single source of truth for anyone who needs to understand or integrate with your API.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>api</category>
      <category>webassembly</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Running Cloudflare Workers Inside Spin Apps</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Thu, 14 Aug 2025 01:37:00 +0000</pubDate>
      <link>https://forem.com/fermyon/running-cloudflare-workers-inside-spin-apps-2bjb</link>
      <guid>https://forem.com/fermyon/running-cloudflare-workers-inside-spin-apps-2bjb</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Matt Butcher&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are many kinds of serverless functions available. Cloudflare Workers are one popular form of function designed to run on the edge. But in many cases, you can compile, load, and execute Cloudflare Workers within Spin apps.&lt;/p&gt;

&lt;p&gt;In this post, we’ll see one strategy that lets you embed an entire Cloudflare Worker inside of a Spin app, giving you the option to deploy the Worker to Cloudflare or deploy the entire app into any Spin-compatible runtime including &lt;a href="https://www.spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt; (that is, Kubernetes), &lt;a href="https://www.fermyon.com/cloud" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt;, or Akamai via &lt;a href="https://developer.fermyon.com/wasm-functions/index" rel="noopener noreferrer"&gt;Wasm Functions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Before we dive into the procedural bits, its important to understand the primary difference between Cloudflare Workers and Spin apps. Cloudflare provides a &lt;em&gt;JavaScript interpreter&lt;/em&gt; to run JS files. Specifically, it uses the V8 engine that powers the Chrome family of browsers. When Cloudflare runs a Worker, it loads the worker JS files into the interpreter and executes them.&lt;/p&gt;

&lt;p&gt;In contrast, Spin executes WebAssembly binaries. Each time we run &lt;code&gt;spin build&lt;/code&gt;, we are compiling a binary file that will be executed. With a JavaScript app, what we’re actually doing is embedding the SpiderMonkey (Mozilla) JS runtime, loading the scripts into the interpreter, and then compiling that whole thing to WebAssembly. So there are no &lt;code&gt;.js&lt;/code&gt;files in the package that we run. It’s all just WebAssembly byte codes.&lt;/p&gt;

&lt;p&gt;The technique we use in this blog post calls out to the Workers as libraries. And that means that at &lt;code&gt;spin build&lt;/code&gt; time, the worker will be compiled (alongside the rest of the Spin code) into that WebAssembly binary.&lt;/p&gt;

&lt;p&gt;With the conceptual details out of the way, it’s time to dive into the code. We are assusme the &lt;a href="https://spinframework.dev/v3/quickstart" rel="noopener noreferrer"&gt;installation&lt;/a&gt; of both Spin and NPM. That should be all you need to get going.&lt;/p&gt;

&lt;p&gt;We’ll first create a Spin JavaScript app. Then, inside that app, we’ll create a Cloudflare Worker. We’ll verify that we can run that Worker locally, and then we’ll wire up Spin to route traffic to that worker. When we build the Spin app, we’ll have a single Wasm artifact that contains the Spin router and the Worker all in one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Spin App
&lt;/h3&gt;

&lt;p&gt;There is nothing special about creating a Spin app that will call out to a Cloudflare worker. We can follow the standard process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ spin new -t http-js --accept-defaults spin-worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above will silently create a new &lt;code&gt;spin-worker/&lt;/code&gt; directory and add all of the necessary Spin files.&lt;/p&gt;

&lt;p&gt;At this point, we can &lt;code&gt;cd&lt;/code&gt; into that directory and run &lt;code&gt;spin build&lt;/code&gt; to validate that the project can build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd spin-worker &amp;amp;&amp;amp; spin build                     
Building component spin-worker with `npm install`

...

Component successfully written.
Finished building all Spin components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By starting a server and using &lt;code&gt;curl&lt;/code&gt; to send a request, we can verify the output of our new app. In one terminal, run &lt;code&gt;spin up&lt;/code&gt; to start the server. In another, access the server with &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:3000
hello universe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we’re ready to create a new Worker inside of our Spin app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Worker
&lt;/h3&gt;

&lt;p&gt;The official &lt;a href="https://developers.cloudflare.com/workers/get-started/guide/" rel="noopener noreferrer"&gt;Cloudflare guide&lt;/a&gt; describes how to get started with workers. We’ll follow the same process.&lt;/p&gt;

&lt;p&gt;Still working inside of the &lt;code&gt;spin-worker&lt;/code&gt; directory, let’s create a new Cloudflare Worker called &lt;code&gt;cf-worker&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm create cloudflare@latest -- cf-worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will kick of a series of prompts. You should answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Category: &lt;code&gt;Hello World example&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Type: &lt;code&gt;Worker only&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Language: &lt;code&gt;JavaScript&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Git: &lt;code&gt;No&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deploy your application: &lt;code&gt;No&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, you should have a new subdirectory inside of &lt;code&gt;spin-worker&lt;/code&gt; called &lt;code&gt;cf-worker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just to validate that this is a real worker and can be executed, we can run Wrangler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cd cf-worker &amp;amp;&amp;amp; npx wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above will start a server on &lt;code&gt;localhost:8787&lt;/code&gt; (convenient, since it does not clash with Spin’s use of port 3000). Using &lt;code&gt;curl&lt;/code&gt; in another terminal, we can see the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:8787                                                                                                                                            
Hello World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s do a quick modification of the Worker so that we’ll be able to easily tell later that the worker is indeed being executed. In s&lt;code&gt;pin-worker/cf-worker/src/index.js&lt;/code&gt;, let’s edit the code to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default {
    async fetch(request, env, ctx) {
        return new Response('Hello from a worker');
    },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve updated the response to say &lt;code&gt;Hello from a worker&lt;/code&gt; instead of &lt;code&gt;Hello World!&lt;/code&gt;. Let’s rebuild and test by running that Wrangler command again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx wrangler dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we get the following output from &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:8787
Hello from a worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re ready to edit our Spin app now to call this worker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling the Worker from Spin
&lt;/h3&gt;

&lt;p&gt;Let’s work on the Spin JavaScript code in &lt;code&gt;spin-worker/src/index.js&lt;/code&gt;. First off, let’s do the simple thing and change the default &lt;code&gt;Hello universe&lt;/code&gt; message to something clearer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AutoRouter } from 'itty-router';

let router = AutoRouter();

router
    .get("/", () =&amp;gt; new Response("Hello from Spin"))

addEventListener('fetch', async (event) =&amp;gt; {
    event.respondWith(router.fetch(event.request));
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can run our Spin app with &lt;code&gt;spin build --up&lt;/code&gt;, and then use &lt;code&gt;curl&lt;/code&gt; to see the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:3000 
Hello from Spin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we’re using a different URL than we were when testing the Worker. We’re on port &lt;code&gt;3000&lt;/code&gt; instead of &lt;code&gt;8787&lt;/code&gt;. When we hit the default route (/), we get the &lt;code&gt;Hello from Spin&lt;/code&gt; message.&lt;/p&gt;

&lt;p&gt;With a couple of modifications to our code, we can create a second route, called &lt;code&gt;/worker&lt;/code&gt; that executes the worker and returns the result. Once more, editing the &lt;code&gt;spin-worker/src/index.js file&lt;/code&gt;, we will add a few lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { AutoRouter } from 'itty-router';
import Worker from '../cf-worker/src/index'

let router = AutoRouter();

router
    .get("/", () =&amp;gt; new Response("Hello from Spin"))
    // When a client requests /worker, run the cf-worker function
    .get("/worker", Worker.fetch)

addEventListener('fetch', async (event) =&amp;gt; {
    event.respondWith(router.fetch(event.request));
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two important bits above.&lt;/p&gt;

&lt;p&gt;First, we imported &lt;code&gt;Worker&lt;/code&gt; from &lt;code&gt;../cf-workers/src/index&lt;/code&gt;. This line imports everything exported out of the worker. In our Worker code, we declared only one function, &lt;code&gt;fetch&lt;/code&gt;. So by importing this way, we can now execute that function using &lt;code&gt;Worker.fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, in the &lt;code&gt;router&lt;/code&gt;, we add a new route, &lt;code&gt;/worker&lt;/code&gt; that, when called, will run &lt;code&gt;Worker.fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we can rebuild our Spin app and test it again using &lt;code&gt;spin build --up&lt;/code&gt;. This time, we can run &lt;code&gt;curl&lt;/code&gt; twice to verify that we can hit the main route and get &lt;code&gt;Hello from Spin&lt;/code&gt; and then hit &lt;code&gt;/worker&lt;/code&gt; and get &lt;code&gt;Hello from a worker&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:3000
Hello from Spin
$ curl localhost:3000/worker
Hello from a worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s the basics for how to call a Worker from within a Spin app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying a Worker to a Spin runtime
&lt;/h2&gt;

&lt;p&gt;So far, we have run everything locally. It is easy to run this Spin-wrapped Worker on &lt;a href="https://www.spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt; (Kubernetes), &lt;a href="https://www.fermyon.com/cloud" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/aks/deploy-spinkube" rel="noopener noreferrer"&gt;Azure AKS&lt;/a&gt;, &lt;a href="https://www.spinkube.dev/docs/install/rancher-desktop/" rel="noopener noreferrer"&gt;Rancher Desktop&lt;/a&gt;, and other places. For this example, we’re going to deploy the Spin app to Akamai using &lt;a href="https://www.fermyon.com/wasm-functions" rel="noopener noreferrer"&gt;Fermyon Wasm Functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To do this, I will use the &lt;a href="https://developer.fermyon.com/wasm-functions/index" rel="noopener noreferrer"&gt;Spin Akamai plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From inside of the &lt;code&gt;spin-worker&lt;/code&gt; directory, we just use the &lt;code&gt;spin aka deploy&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ spin aka deploy
App 'spin-worker' initialized successfully.
Waiting for application to be ready... ready

View application:   
https://f1f1b106-edea-4b45-85c1-38de0c06aa2b.aka.fermyon.tech/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, I can now access my application once more using curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl https://f1f1b106-edea-4b45-85c1-38de0c06aa2b.aka.fermyon.tech/       
Hello from Spin
$ curl https://f1f1b106-edea-4b45-85c1-38de0c06aa2b.aka.fermyon.tech/worker 
Hello from a worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For directions on deploying to other environments, see the &lt;a href="https://spinframework.dev/v3/deploying" rel="noopener noreferrer"&gt;Spin Deployment Options&lt;/a&gt; documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;While many Cloudflare Workers can be run as easily as we showed above, there are a number of APIs that do not match between Workers and Spin apps. In a later post, we’ll cover how to write a more sophisticated app that can select resources like key value storage based on the runtime. With some careful code building, you can continue to manage one code base that combines both Spin and Workers.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>webassembly</category>
      <category>spinkube</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Supercharging Spin Applications with Wizer</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Thu, 14 Aug 2025 01:16:26 +0000</pubDate>
      <link>https://forem.com/fermyon/supercharging-spin-applications-with-wizer-1hig</link>
      <guid>https://forem.com/fermyon/supercharging-spin-applications-with-wizer-1hig</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Thorsten Hans&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In today’s world, the quest for ultra-fast, low-latency applications is relentless. As the demand for real-time user experiences grows, optimizing serverless applications for runtime performance is not just desirable, but essential. Enter &lt;a href="https://github.com/bytecodealliance/wizer" rel="noopener noreferrer"&gt;Wizer&lt;/a&gt; - the WebAssembly pre-initializer that is redefining the way we build and deploy lightning-fast workloads. In this post, we’ll explore how Wizer can be leveraged to compile data directly into Spin applications, with a hands-on use-case: delivering geo location lookup from a client’s IP address.&lt;/p&gt;

&lt;p&gt;Our stack? &lt;a href="https://www.fermyon.com/blog/why-we-chose-rust-for-spin" rel="noopener noreferrer"&gt;We use Rust&lt;/a&gt; for implementing the Spin application, and we’ll deploy it to &lt;a href="https://www.fermyon.com/blog/wasm-functions" rel="noopener noreferrer"&gt;Fermyon Wasm Functions&lt;/a&gt; — the world’s fastest serverless compute platform—running on Akamai Cloud, the planet’s largest and speediest network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Wizer?
&lt;/h2&gt;

&lt;p&gt;Traditional serverless and microservice architectures often grapple with the classic cold-start problem and data loading overheads. Thanks to WebAssembly’s inherent speed and the ultra-efficient, high-density runtime powering Fermyon Wasm Functions, the typical cold start dilemma simply doesn’t exist in Fermyon Wasm Functions. Applications launch instantly, making real-time responsiveness the norm.&lt;/p&gt;

&lt;p&gt;However, when your workloads rely on external data stores, you inevitably introduce latency—every remote fetch or database query adds precious milliseconds to your compute duration and undermines that hard-won performance edge.&lt;/p&gt;

&lt;p&gt;Traditional serverless and microservice architectures have long struggled with these data access delays. Even as Wasm shaves execution time to the bare minimum, each round trip to a database can erode the benefits of instant startup. This emphasizes the need for new approaches to data handling when striving for truly lightning-fast applications.&lt;/p&gt;

&lt;p&gt;That’s where Wizer shines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-initialized State&lt;/strong&gt;: Wizer allows you to snapshot your application state—including loaded data structures—at build time. When your Wasm module gets instantiated, you’re already primed for action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No External Dependency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Latency:&lt;/strong&gt; By baking data directly into the Wasm binary, your workload can respond to requests in record time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Taking into context that Spin apps running on Fermyon Wasm Functions are distributed around the globe and incoming requests are routed to the closest data center, integrating data into the application binary is even more important. In a globally distributed setup like this, relying on a non-globally distributed external service (e.g., a hosted database or blob storage) will have a dramatic impact for a huge fraction of users, as their requests may travel across the globe for querying necessary data from the external service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Use-Case: Lightning-Fast GeoIP Lookup
&lt;/h2&gt;

&lt;p&gt;Imagine needing to determine a user’s country, city, or region based solely on their IP address. This is a common requirement for content localization, regulatory compliance, analytics, and more. The challenge? Most IP geolocation databases, like MaxMind’s GeoLite2, are massive and expensive to parse at runtime.&lt;/p&gt;

&lt;p&gt;What if you could bake the entire geolocation database directly into your application binary, and serve geo lookups in just a few milliseconds—no database connections, no cold data fetches? With Spin and Wizer, you can!&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Data Into a Spin Application
&lt;/h3&gt;

&lt;p&gt;You can find the entire source code of the GeoIP-Lookup sample over on GitHub at &lt;a href="https://github.com/fermyon/fwf-examples/tree/main/samples/geo-ip" rel="noopener noreferrer"&gt;fermyon/fwf-examples/tree/main/samples/geo-ip&lt;/a&gt;. We followed four steps for integrating the database into the application binary with Wizer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We downloaded a database compatible with the &lt;a href="https://crates.io/crates/maxminddb" rel="noopener noreferrer"&gt;maxminddb crate&lt;/a&gt; and place it in the &lt;code&gt;geoip-static-db&lt;/code&gt; directory of the Spin application&lt;/li&gt;
&lt;li&gt;We defined an &lt;code&gt;init&lt;/code&gt; function that loads the database and stores it in a static variable&lt;/li&gt;
&lt;li&gt;We ran &lt;code&gt;wizer&lt;/code&gt; to pre-initialize the Wasm module. Wizer executes the &lt;code&gt;init&lt;/code&gt; function at build-time, snapshots the in-memory state (including the loaded geo database), and emits a pre-initialized Wasm module.&lt;/li&gt;
&lt;li&gt;We deployed the resulting - pre-initialized - Spin application to Fermyon Wasm Functions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Under the Hood: Browsing the source code
&lt;/h2&gt;

&lt;p&gt;Let’s go one layer deeper and look at the actual source code, invoked by Wizer and required to store the actual data in the Wasm binary. We’re using the &lt;code&gt;maxminddb&lt;/code&gt; crate for Rust allowing us to load the database and execute queries against it.&lt;/p&gt;

&lt;p&gt;The database itself is held in memory using a static variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use maxminddbReader;
use std::sync::OnceLock;

static DB: OnceLock&amp;lt;Reader&amp;lt;Vec&amp;lt;u8&amp;gt;&amp;gt;&amp;gt; = OnceLock::new();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exported &lt;code&gt;init&lt;/code&gt; function (which will be invoked by wizer at build-time, is responsible for loading the MaxMind database from the disk and stores it in our static &lt;code&gt;DB&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[export_name = "wizer.initialize"]
pub extern "C" fn init() {
    let mut args = String::new();
    std::io::stdin()
        .read_line(&amp;amp;mut args)
        .expect("failed to read stdin");
    let args = args.trim().split_whitespace().collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;();

    match args[..] {
        [mmdb_path] =&amp;gt; {
            println!("Loading maxminddb file from {mmdb_path}");
            let db = maxminddb::Reader::open_readfile(mmdb_path)
                .expect("Failed to open {mmdb_path}");
            DB.set(db).expect("Failed to set DB");
        }
        _ =&amp;gt; {
            panic!("Expected one argument: &amp;lt;mmdb_path&amp;gt;");
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;geoip-static-db&lt;/code&gt; Wasm component encapsulates data and lookup logic from the HTTP API, and exposes the &lt;code&gt;lookup&lt;/code&gt; interface - as defined in WIT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package fermyon:geoip;

interface lookup {
    lookup: func(ip: string) -&amp;gt; result&amp;lt;location, error&amp;gt;;
    record location {
        country: string,
        city: string,
        latitude: f64,
        longitude: f64,
    }

    enum error {
        invalid-ip,
        not-found,
        internal,
    }
}

world geoip {
    export lookup;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation of the &lt;code&gt;lookup&lt;/code&gt; function is straightforward thanks to the &lt;code&gt;maxminddb&lt;/code&gt; crate. The provided IP address is parsed into the &lt;code&gt;IpAddr&lt;/code&gt; structure (&lt;code&gt;std::net::IpAddr&lt;/code&gt;), which we can use to query the city, country, longitude and latitude and create a response according to the &lt;code&gt;location&lt;/code&gt; record defined in WIT:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn lookup(ip: String) -&amp;gt; Result&amp;lt;Location, Error&amp;gt; {
  let db = DB.get().ok_or(Error::Internal)?;
  let ip_addr: IpAddr = ip.parse().map_err(|_| Error::InvalidIp)?;
  let record: geoip2::City = db
    .lookup(ip_addr)
    .map_err(|e| {
      eprintln!("Error looking up IP: {e}");
      Error::Internal
     })?.ok_or(Error::NotFound)?;

  let city = record
    .city
    .and_then(|city| city.names.and_then(|names| names.get("en").cloned()))
    .filter(|name| !name.is_empty())
    .unwrap_or_else(|| "Unknown");

  let country = record
    .country
    .and_then(|country| country.names.and_then(|names| names.get("en").cloned()))
    .filter(|name| !name.is_empty())
    .unwrap_or_else(|| "Unknown");

  let longitude = record
    .location
    .as_ref()
    .and_then(|loc| loc.longitude)
    .unwrap_or(0.0);

  let latitude = record.location.and_then(|loc| loc.latitude).unwrap_or(0.0);

  Ok(Location {
    city: city.to_string(),
    country: country.to_string(),
    longitude,
    latitude,
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;lookup&lt;/code&gt; interface implemented, the HTTP API component can simply grab the client’s IP address from the &lt;code&gt;true-client-ip&lt;/code&gt; request header and call the &lt;code&gt;lookup&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[http_component]
fn handle_hello_geoip(req: Request) -&amp;gt; anyhow::Result&amp;lt;impl IntoResponse&amp;gt; {
    let ip = req.header("true-client-ip")
        .expect("true-client-ip")
        .as_str()
        .unwrap();

    let res = fermyon::geoip::lookup::lookup(ip)?;

    Ok(Response::builder()
        .status(200)
        .header("content-type", "text/plain")
        .body(format!(
            "IP: {}\\nCountry: {}\\nCity: {}\\nLatitude: {}\\nLongitude: {}",
            ip, res.country, res.city, res.latitude, res.longitude
        ))
        .build())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building and Testing the Spin Application
&lt;/h2&gt;

&lt;p&gt;For compiling the source code to Wasm and to perform the pre-initialization, the following tools must be installed on your machine&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust - Including &lt;code&gt;thewasm32-wasip1&lt;/code&gt; target&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wizer&lt;/code&gt; - You can install Wizer using &lt;code&gt;cargo install wizer --all-features&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wget&lt;/code&gt; - For downloading a MaxMindDB compliant database&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm-opt&lt;/code&gt; (optional) - You can install &lt;code&gt;wasm-opt&lt;/code&gt;
using &lt;code&gt;cargo install wasm-opt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Download a MaxMindDB compliant sample database from &amp;lt;https://github.com/P3TERX/GeoLite.mmdb&amp;gt;
wget &amp;lt;https://git.io/GeoLite2-City.mmdb&amp;gt;

# Move the database to the desired location
mv GeoLite2-City.mmdb geoip-static-db/geoip.mmdb

# Run the build script to
# - Compile the Rust source code
# - Execute Wizer
# - Optimize Wasm binary file size if wasm-opt is installed
make build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once application compilation has finished, you can run the app on your local machine using &lt;code&gt;spin up&lt;/code&gt;. When sending requests to app, ensure to provide the desired IP address using the &lt;code&gt;true-client-ip&lt;/code&gt; HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -H 'true-client-ip:46.165.131.203' localhost:3000

IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying to Fermyon Wasm Functions
&lt;/h2&gt;

&lt;p&gt;For deploying the application to Fermyon Wasm Functions, you must be authenticated. (&lt;code&gt;spin aka login&lt;/code&gt;). Once authenticated, deployment is as easy as executing the &lt;code&gt;spin aka deploy&lt;/code&gt; command and waiting for the app to be deployed across all service regions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka deploy --create-name geoip-lookup --no-confirm

Workspace linked to app geoip-lookup
Waiting for app to be ready... ready

App Routes:
- geoip-example: &amp;lt;https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech&amp;gt; (wildcard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once app deployment has been finished you can start sending requests to the generated endpoint and see how fast our Spin app is able to locate you, based on your IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl &amp;lt;https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech&amp;gt;
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524

time curl &amp;lt;https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech&amp;gt;
IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
curl &amp;lt;https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech&amp;gt;  0.01s user 0.01s system 4% cpu 0.083 total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the output from &lt;code&gt;time curl ...&lt;/code&gt; indicating that the end-to-end execution time is as little as 83ms! But we can get more insights. Just sent another &lt;code&gt;curl&lt;/code&gt; request to the app and provide the &lt;code&gt;-i&lt;/code&gt; flag to print all response headers to &lt;code&gt;stdout&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -i &amp;lt;https://86d3582b-a318-431f-bc7a-e26a2350137b.aka.fermyon.tech&amp;gt;
HTTP/1.1 200 OK
Content-Type: text/plain
x-envoy-upstream-service-time: 1
Server: envoy
Date: Fri, 04 Jul 2025 11:32:33 GMT
Content-Length: 90
Connection: keep-alive

IP: 46.165.131.203
Country: Germany
City: Siebeldingen
Latitude: 49.2059
Longitude: 8.0524
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the &lt;code&gt;x-envoy-upstream-service-time&lt;/code&gt; HTTP header, indicating the pure compute time within Fermyon Wasm Functions is below 1ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By combining the Wasm pre-initialization with Wizer, Spin and the incredible speed provided by Fermyon Wasm Functions, you can deliver serverless applications with performance once thought impossible. Whether you’re building geo-aware content, compliance solutions, or real-time request processing, this stack lets you optimize for pure speed.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>rust</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a GraphQL API with Fermyon Wasm Functions</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Mon, 28 Jul 2025 19:19:30 +0000</pubDate>
      <link>https://forem.com/fermyon/building-a-graphql-api-with-fermyon-wasm-functions-ae7</link>
      <guid>https://forem.com/fermyon/building-a-graphql-api-with-fermyon-wasm-functions-ae7</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: MacKenzie Adam&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GraphQL has revolutionized how we think about APIs, offering developers precise control over data fetching and reducing over-fetching issues common with REST APIs. When combined with the lightning-fast startup times and efficiency of WebAssembly, GraphQL becomes even more powerful for serverless applications.&lt;/p&gt;

&lt;p&gt;In this blog, we’ll walk through building a complete GraphQL client using Fermyon Wasm Functions that queries GitHub’s GraphQL API to fetch and report repository stargazer information. By the end, you’ll have a globally distributed, fully functional serverless application that demonstrates the power of combining type-safe GraphQL queries with WebAssembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We’re Building
&lt;/h2&gt;

&lt;p&gt;We’ll create a serverless GraphQL client as a Spin application that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Rust’s type-safe GraphQL client generation&lt;/li&gt;
&lt;li&gt;Demonstrates secure API token handling in serverless environments&lt;/li&gt;
&lt;li&gt;Queries GitHub’s GraphQL API for repository stargazer counts&lt;/li&gt;
&lt;li&gt;Renders results as a simple HTML page&lt;/li&gt;
&lt;li&gt;Runs on the Fermyon Wasm Functions platform with sub-millisecond cold starts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive in, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.fermyon.com/spin/install" rel="noopener noreferrer"&gt;Spin CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;Rust toolchain with &lt;code&gt;wasm32-wasip1&lt;/code&gt; target&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/settings/personal-access-tokens" rel="noopener noreferrer"&gt;GitHub personal access token&lt;/a&gt; with repository read permissions&lt;/li&gt;
&lt;li&gt;Basic familiarity with GraphQL and Rust&lt;/li&gt;
&lt;li&gt;Access to either &lt;a href="https://fibsu0jcu2g.typeform.com/fwf-preview" rel="noopener noreferrer"&gt;Fermyon Wasm Functions&lt;/a&gt; platform (used in this tutorial) or &lt;a href="https://developer.fermyon.com/cloud/quickstart" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;To deploy this on Fermyon Wasm Functions, you must request access to &lt;a href="https://fibsu0jcu2g.typeform.com/fwf-preview" rel="noopener noreferrer"&gt;the service&lt;/a&gt;. If you do not have global distribution requirements, you can follow along using &lt;a href="https://developer.fermyon.com/cloud/quickstart" rel="noopener noreferrer"&gt;Fermyon Cloud&lt;/a&gt; instead which offers a free developer tier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting Up Your Development Environment
&lt;/h2&gt;

&lt;p&gt;First, let’s clone the sample which is available in the &lt;a href="https://github.com/fermyon/fwf-examples" rel="noopener noreferrer"&gt;Fermyon Wasm Functions example repository&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/fermyon/fwf-examples.git
cd samples/graphql-stargazer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick check of the folder reveals a typical Spin application directory structure with a source code directory, &lt;code&gt;spin.toml&lt;/code&gt; (application manifest), and &lt;code&gt;cargo.toml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls
Cargo.lock  Cargo.toml  README.md   spin.toml   src     target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the GraphQL Schema and Query
&lt;/h2&gt;

&lt;p&gt;Our application uses GitHub’s public GraphQL API. In this sample, for the sake of simplicity, we’re only interested in extracting the stargazers count for a particular repository.&lt;/p&gt;

&lt;p&gt;For our stargazer query, we create a focused query file &lt;code&gt;src/query_1.graphql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query RepoView($owner: String!, $name: String!) {
  repository(owner: $owner, name: $name) {
    homepageUrl
    stargazers {
      totalCount
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query accepts two variables (repository owner and name) and returns the total count of stargazers along with the homepage URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the GraphQL Client
&lt;/h2&gt;

&lt;p&gt;Let’s build our implementation step by step, focusing on the key concepts that make this approach powerful. If you’re following along locally, navigate to &lt;code&gt;src/lib.rs&lt;/code&gt; as we dive into the application logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type-Safe GraphQL Code Generation
&lt;/h3&gt;

&lt;p&gt;The magic of our implementation starts with Rust’s type-safe GraphQL code generation. Using the &lt;code&gt;graphql_client&lt;/code&gt; crate, we can generate compile-time validated Rust code from our GraphQL schema and queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use graphql_client::GraphQLQuery;

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "src/schema.graphql",
    query_path = "src/query_1.graphql",
    response_derives = "Debug"
)]
struct RepoView;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single derive macro does a lot of work behind the scenes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates type-safe Rust structs from your GraphQL schema&lt;/li&gt;
&lt;li&gt;Creates query builder methods like &lt;code&gt;RepoView::build_query()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Provides compile-time validation that your queries match the schema&lt;/li&gt;
&lt;li&gt;Handles all serialization/deserialization automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated code creates a &lt;code&gt;repo_view::Variables&lt;/code&gt; struct for query parameters and &lt;code&gt;repo_view::ResponseData&lt;/code&gt; for the response, ensuring you can never send malformed queries or access non-existent fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure Token Management with Spin Variables
&lt;/h3&gt;

&lt;p&gt;Instead of hardcoding API tokens, we use &lt;a href="https://spinframework.dev/v3/variables#using-variables-from-applications" rel="noopener noreferrer"&gt;Spin’s secure variable system&lt;/a&gt; which you can see in this snippet from &lt;code&gt;lib.rs&lt;/code&gt; here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let github_api_token = spin_sdk::variables::get("gh_api_token")
    .expect("Missing gh_api_token variable");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ll review in more detail how this variable is set later on. By using this approach, we ensure tokens are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never stored in source code&lt;/li&gt;
&lt;li&gt;Injected securely at runtime&lt;/li&gt;
&lt;li&gt;Easy to rotate without code changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Building and Sending the GraphQL Request
&lt;/h3&gt;

&lt;p&gt;Once we have our query variables, building the request is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create variables for our GraphQL query using the generated types
let variables = repo_view::Variables {
    owner: owner.to_string(),
    name: name.to_string(),
};

// Build the GraphQL query structure with our variables
let body = RepoView::build_query(variables);
// Serialize the query to JSON for the HTTP request
let body = serde_json::to_string(&amp;amp;body).unwrap();

// Construct the HTTP POST request to GitHub's GraphQL endpoint
let outgoing = Request::post("https://api.github.com/graphql", body)
    .header("user-agent", "graphql-rust")         // GitHub requires a user-agent
    .header("content-type", "application/json")   // GraphQL queries are sent as JSON
    .header("Authorization", format!("Bearer {}", github_api_token))  // API authentication
    .build();

// Send the request and await the response
let res: Response = send(outgoing).await?;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type-safe &lt;code&gt;build_query()&lt;/code&gt; method ensures we’re sending exactly the right GraphQL query structure to GitHub’s API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Processing the GraphQL Response
&lt;/h3&gt;

&lt;p&gt;The response handling showcases the utility of generated types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Parse the GraphQL response into our generated type-safe structs
let response: graphql_client::Response&amp;lt;repo_view::ResponseData&amp;gt; =
    serde_json::from_slice(res.body()).unwrap();
// Extract the actual data from the GraphQL response wrapper
let response_data = response.data.expect("missing response data");

// Navigate the response with complete type safety
// The compiler ensures these fields exist and have the right types
let stars = response_data
    .repository
    .as_ref()  // Handle the case where repository might be null (not found)
    .map(|repo| repo.stargazers.total_count);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we can navigate the response with complete type safety: &lt;code&gt;response_data.repository.stargazers.total_count&lt;/code&gt; is validated at compile time to match our GraphQL schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL Parsing and Error Handling
&lt;/h3&gt;

&lt;p&gt;Our application expects URLs like &lt;code&gt;/fermyon/finicky-whiskers&lt;/code&gt; and extracts the repository information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn parse_repo_name(repo_name: &amp;amp;str) -&amp;gt; Result&amp;lt;(&amp;amp;str, &amp;amp;str), anyhow::Error&amp;gt; {
    // Split the path on '/' and skip the first empty element
    // For "/fermyon/finicky-whiskers", this gives us ["", "fermyon", "finicky-whiskers"]
    let mut parts = repo_name.split('/').skip(1);

    // Extract owner and repository name, ensuring both exist
    match (parts.next(), parts.next()) {
        (Some(owner), Some(name)) =&amp;gt; Ok((owner, name)),
        _ =&amp;gt; Err(format_err!(
            "wrong format for the repository name param (we expect something like spinframework/spin)"
        ))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration with &lt;code&gt;spin.toml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we’ve completed our application logic, let’s take a look at how our application’s capabilities and constraints are expressed in the application manifest (&lt;code&gt;spin.toml&lt;/code&gt;). Our &lt;code&gt;spin.toml&lt;/code&gt; file configures the application with proper security constraints. Here we have a single component application that is invoked with an HTTP trigger. This component has the capability to make an outbound HTTP call and can access a variable called &lt;code&gt;gh_api_token&lt;/code&gt;, which we used above to make calls to GitHub’s public API. This is the value that get passed into our &lt;code&gt;lib.rs&lt;/code&gt;file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin_manifest_version = 2

[application]
name = "graphql"
version = "0.1.0"
authors = ["Till Schneidereit &amp;lt;till@tillschneidereit.net&amp;gt;"]
description = "GraphQL experiments"

[variables]
gh_api_token = { secret = true, required = true }

[[trigger.http]]
route = "/..."
component = "graphql"

[component.graphql]
source = "target/wasm32-wasip1/release/graphql.wasm"
allowed_outbound_hosts = ["https://api.github.com"]
variables = { gh_api_token = "{{ gh_api_token }}" }
[component.graphql.build]
command = "cargo build --target wasm32-wasip1 --release"
watch = ["src/**/*.rs", "Cargo.toml"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secret variables&lt;/strong&gt;: The GitHub token is marked as &lt;code&gt;secret = true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound host restriction&lt;/strong&gt;s: Only &lt;code&gt;https://api.github.com&lt;/code&gt; is allowed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure by default&lt;/strong&gt;: WebAssembly’s sandbox model provides additional isolation such as preventing access to the filesystem unless explicitly granted to the Spin app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building and Testing Locally
&lt;/h2&gt;

&lt;p&gt;To run the application locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install the WebAssembly target if you haven't already
rustup target add wasm32-wasip1

# Build the application
spin build

# Run with your GitHub token
SPIN_VARIABLE_GH_API_TOKEN=[your-token-here] spin up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you must set the variable name exactly as above to pass in the &lt;a href="https://spinframework.dev/v3/variables#application-variables" rel="noopener noreferrer"&gt;variable when running a Spin app locally&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you can test it by visiting URLs that follow this format, corresponding to the desired GitHub organization and repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:3000/gh-org/gh-repo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl localhost:3000/fermyon/finicky-whiskers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each will show you the stargazer count for that repository!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie4t2f3n2roa3ovtgo10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fie4t2f3n2roa3ovtgo10.png" alt="Simple HTML rendered by Spin app" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Fermyon Wasm Functions
&lt;/h2&gt;

&lt;p&gt;Deployment is straightforward with Fermyon Wasm Functions. With one simple command, your application will be globally distributed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Log into Fermyon Wasm Functions if you haven't already
spin aka login

# Deploy with your GitHub token
spin aka deploy --variable gh_api_token=[your-token-here]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deployment will provide you with a unique URL where your GraphQL client is running in production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;...&amp;gt;                      
App Routes:
- graphql: https://87f9c916-331c-4a44-b7ad-d2a8331185ff.aka.fermyon.tech (wildcard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you’re following along using Fermyon Cloud, you can find steps on how to deploy &lt;a href="https://developer.fermyon.com/cloud/deploy" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building GraphQL clients with Fermyon Wasm Functions combines the precision of GraphQL with the performance benefits of WebAssembly. This approach is particularly powerful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs requiring type safety and compile-time validation&lt;/li&gt;
&lt;li&gt;Applications with unpredictable traffic patterns&lt;/li&gt;
&lt;li&gt;Microservices architectures requiring fast startup times&lt;/li&gt;
&lt;li&gt;Teams wanting to leverage Rust’s ecosystem for API clients&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The serverless nature of Wasm Functions ensures your applications run fast and secure on Fermyon Wasm Functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources and Further Reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.fermyon.com/wasm-functions" rel="noopener noreferrer"&gt;Fermyon Wasm Functions Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spinframework.dev/v3/http-trigger" rel="noopener noreferrer"&gt;Spin Framework HTTP Triggers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/graphql" rel="noopener noreferrer"&gt;GitHub GraphQL API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/graphql_client/" rel="noopener noreferrer"&gt;graphql_client Crate Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.fermyon.com/spin/otel" rel="noopener noreferrer"&gt;Spin Observability with OpenTelemetry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>graphql</category>
      <category>webassembly</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Running Serverless Wasm Functions on the Edge with k3s and SpinKube</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Fri, 25 Jul 2025 03:43:28 +0000</pubDate>
      <link>https://forem.com/fermyon/running-serverless-wasm-functions-on-the-edge-with-k3s-and-spinkube-chi</link>
      <guid>https://forem.com/fermyon/running-serverless-wasm-functions-on-the-edge-with-k3s-and-spinkube-chi</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Matt Butcher&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally presented at &lt;a href="https://www.suse.com/susecon/" rel="noopener noreferrer"&gt;SUSECON&lt;/a&gt; 25 by Matt Butcher, CEO of Fermyon Technologies. Watch the full presentation on &lt;a href="https://www.youtube.com/watch?v=SXApMGA9oU8" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The cloud computing landscape has evolved through distinct phases: from one-to-one hardware-OS relationships, to virtual machines enabling multiple operating systems per machine, to containers providing lightweight process isolation. Today, we’re witnessing the emergence of a fourth paradigm: WebAssembly (Wasm) in the cloud — and it’s perfectly suited for serverless workloads on Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional Serverless
&lt;/h2&gt;

&lt;p&gt;While containers revolutionized how we package and deploy long-running services like NGINX, PostgreSQL, and API servers, they fall short for serverless workloads. Both virtual machines and containers suffer from the same fundamental limitation: startup times of 12+ seconds, sometimes stretching into minutes.&lt;/p&gt;

&lt;p&gt;This latency makes true serverless difficult to achieve. Current solutions like AWS Lambda and Azure Functions work around this by maintaining huge queues of pre-warmed virtual machines — hardly an efficient approach for handling event-driven workloads where requests should trigger handlers that start, execute, and shut down quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WebAssembly is Perfect for Serverless
&lt;/h2&gt;

&lt;p&gt;WebAssembly wasn’t originally designed for the cloud, but its browser-oriented features make it ideal for serverless functions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Security First&lt;/strong&gt;&lt;br&gt;
Web browsers are arguably our most trusted software — we visit countless websites daily without worrying about system crashes. WebAssembly’s sandbox environment is even more secure than JavaScript’s, using a capability-based system where you can selectively enable or disable features. This is perfect for multi-tenant cloud environments where isolation is critical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Lightning-Fast Cold Starts&lt;/strong&gt;&lt;br&gt;
While Google’s research shows user attention begins to wane after 100 milliseconds of inactivity, WebAssembly was designed for instant execution. In our testing, we’ve achieved 0.5 millisecond cold start times — compared to AWS Lambda’s 100-500 millisecond cold starts. This enables running thousands of applications per node simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Write Once, Run Anywhere&lt;/strong&gt;&lt;br&gt;
Unlike Docker images that require separate builds for ARM and Intel architectures, WebAssembly binaries are truly portable. The same binary runs across any operating system and architecture — including GPUs for AI inferencing workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Language Agnostic&lt;/strong&gt;&lt;br&gt;
About 23 of the top 25 programming languages (according to RedMonk) support WebAssembly compilation. Whether you’re writing in Rust, JavaScript, Python, Ruby, or Go, everything compiles to the same binary format.&lt;/p&gt;
&lt;h3&gt;
  
  
  Introducing Spin and SpinKube
&lt;/h3&gt;

&lt;p&gt;We’ve built two complementary tools to bring WebAssembly into Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://spinframework.dev/" rel="noopener noreferrer"&gt;Spin&lt;/a&gt;: The developer framework for building serverless functions with built-in bindings for key-value storage, relational databases, AI inferencing, and more&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt;: The Kubernetes operator that runs Spin applications natively in your cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both Spin and SpinKube are now part of the &lt;strong&gt;Cloud Native Computing Foundation (CNCF)&lt;/strong&gt;, officially accepted in January 2025. This ensures long-term sustainability and community-driven development.&lt;/p&gt;
&lt;h2&gt;
  
  
  How SpinKube Integrates with Kubernetes
&lt;/h2&gt;

&lt;p&gt;SpinKube isn’t just another container runtime — it’s fully integrated into the Kubernetes ecosystem. When you deploy a Spin application:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The SpinKube operator listens for &lt;code&gt;SpinApp&lt;/code&gt; resources&lt;/li&gt;
&lt;li&gt;Converts them to standard Kubernetes &lt;code&gt;Deployments&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Creates &lt;code&gt;ReplicaSets&lt;/code&gt; and &lt;code&gt;Pods&lt;/code&gt; as usual&lt;/li&gt;
&lt;li&gt;Integrates with containerd via a WebAssembly shim&lt;/li&gt;
&lt;li&gt;Executes Wasm binaries instead of containers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means all your existing Kubernetes tools, volumes, secrets, config maps, and SSL certificates work seamlessly with WebAssembly workloads.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started with Rancher Desktop
&lt;/h2&gt;

&lt;p&gt;The easiest way to try SpinKube is with &lt;a href="https://rancherdesktop.io/" rel="noopener noreferrer"&gt;Rancher Desktop&lt;/a&gt;, which includes built-in support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable containerd&lt;/strong&gt;: In Preferences → Container Engine, select containerd and enable “WebAssembly (wasm) support”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install SpinKube&lt;/strong&gt;: In Preferences → Kubernetes, check “Install Spin operator”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restart&lt;/strong&gt;: Let Rancher Desktop restart to apply changes&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Building Your First Spin Application
&lt;/h2&gt;

&lt;p&gt;Here’s how to create and deploy a simple serverless function:&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Create a New Project
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin new
# Select template: http-ts (TypeScript HTTP handler)
# Project name: hello-kubecon
# Description: Hello KubeCon demo
# HTTP path: /... (handles all routes under /)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Build the Application
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd hello-kubecon
spin build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This compiles your TypeScript into a WebAssembly binary ready for deployment.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Deploy to Kubernetes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Push to OCI registry
spin registry push ttl.sh/hello-kubecon:1h

# Deploy to Kubernetes
spin kube deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  4. Test Your Function
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Port forward to access locally
kubectl port-forward svc/hello-kubecon 8080:80

# Test the endpoint
curl localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The Power of Integration
&lt;/h2&gt;

&lt;p&gt;What makes this approach compelling is that SpinKube applications are first-class Kubernetes citizens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Standard Kubernetes commands work
kubectl get spinapp
kubectl get deployment
kubectl get pods
kubectl get services

# Delete the application
kubectl delete spinapp hello-kubecon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Benefits
&lt;/h2&gt;

&lt;p&gt;The performance characteristics of WebAssembly in Kubernetes are remarkable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0.5ms cold start time&lt;/strong&gt; vs. 100-500ms for traditional serverless&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thousands of concurrent functions&lt;/strong&gt; per node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Efficient scaling&lt;/strong&gt; up and down based on demand&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better resource utilization&lt;/strong&gt; on both small embedded devices and large clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;Ready to explore serverless WebAssembly on Kubernetes? Here’s how to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://spinframework.dev/" rel="noopener noreferrer"&gt;spinframework.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code&lt;/strong&gt;: &lt;a href="https://github.com/fermyon/spin" rel="noopener noreferrer"&gt;github.com/spinframework/spin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt;: Join the CNCF Slack #spin and #spinkube channels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try It&lt;/strong&gt;: Download &lt;a href="https://rancherdesktop.io/" rel="noopener noreferrer"&gt;Rancher Desktop&lt;/a&gt; and follow the setup guide above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/SXApMGA9oU8"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You can watch the full presentation on YouTube.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>webassembly</category>
      <category>spinkube</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Serverless A2A with Spin</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Mon, 07 Jul 2025 17:09:03 +0000</pubDate>
      <link>https://forem.com/fermyon/serverless-a2a-with-spin-41lb</link>
      <guid>https://forem.com/fermyon/serverless-a2a-with-spin-41lb</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Matt Butcher&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In only a few short years, LLMs have gone from a niche technology to the heart of a new wave of AI applications. And even more recently, Model Context Protocol (MCP) has earned buzz by standardizing a way to expose tools to LLMs and reasoning agents. But exposing tools is only a small step in harnessing the power of AI agents. Google recently &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-launches-the-agent2agent-protocol-project-to-enable-secure-intelligent-communication-between-ai-agents" rel="noopener noreferrer"&gt;contributed a new open protocol to Linux Foundation&lt;/a&gt;. A2A (short for Agent-to-Agent opens new opportunities for building agentic systems. In this post we’ll see how easy it is to create Spin apps that implement the A2A protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an Agent?
&lt;/h2&gt;

&lt;p&gt;In AI parlance, an &lt;em&gt;agent&lt;/em&gt; is a piece of code, typically backed by some form of AI, that performs tasks on behalf of a user. As an example, one could envision a &lt;em&gt;travel agent&lt;/em&gt; that received a prompt (“Book me a flight and hotel in Chicago next week.”) and then acted on that prompt, returning a response (“Okay, I booked you the following flight and hotel…”). Sometimes agents are conversational. The simple example above would more likely be carried out over a conversational series (“Agent: What part of Chicago would you like to stay in?”, “User: River North or Downtown”). Sometimes they might return parseable data instead of human text. Or sometimes they might produce audio, images, or video.&lt;/p&gt;

&lt;p&gt;While agents may do things on &lt;em&gt;behalf&lt;/em&gt; of a user, it is not necessarily the case that an agent will directly interact with a user. For example, one recent trend has been to connect agents to GitHub repositories. An agent can perform an action when something in a GitHub repo changes (for example, when an issue is filed or a pull request is created). An agent may be able to complete its job without directly interacting with the GitHub repository owners.&lt;/p&gt;

&lt;p&gt;Even more exciting, though, is agent-to-agent communication. In this case, one agent may leverage other agents it knows about in order to achieve a particular result. That’s where A2A comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is A2A?
&lt;/h2&gt;

&lt;p&gt;A2A is a simple protocol for connecting agents. It has two main functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent discovery: Allow agents to discover each other&lt;/li&gt;
&lt;li&gt;Agent interaction: Define how one agent can send messages to, and receive messages from, another agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The protocol is based on JSON, JSON-RPC, HTTP, and SSE (Server Sent Events), all of which are broadly understood and utilized standards and techniques. This is good news; we can use existing libraries and tools to write our agents.&lt;/p&gt;

&lt;p&gt;Let’s take a look at these two parts of the A2A spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery with Agent Cards
&lt;/h3&gt;

&lt;p&gt;Many existing Web standards perform discovery by the simplest possible means: Agree on a standard location and standard data format for a basic piece of information. For example, &lt;code&gt;robots.txt&lt;/code&gt; files inform web crawlers of how to traverse a site’s content. A file named &lt;code&gt;favicon.ico&lt;/code&gt; provides an icon for site. In both of these cases, humans and applications alike can assume both the location and data format for these files.&lt;/p&gt;

&lt;p&gt;The A2A specification defines a similar technique for allowing agents to discover each other. A JSON formatted file at &lt;code&gt;/.well-known/agent.json&lt;/code&gt; describes the available agent endpoints at a particular server and how those agents can be interacted with.&lt;/p&gt;

&lt;p&gt;For this post, I wrote a simple A2A-style agent that uses Gemini to answer ethical questions using one of the major Western moral philosophies (deontology, virtue ethics, and utilitarianism). Here’s what that agent’s card looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "The Digital Ethicist",
  "description": "This agent analyzes ethical questions based on philopsophical ethics frameworks such as utilitiarism, deontology, or virtue ethics.",
  "url": "http://localhost:41241/api",
  "provider": {
    "organization": "Technosophos"
  },
  "version": "0.1.0",
  "capabilities": {
    "streaming": false,
    "pushNotifications": false
  },
  "defaultInputModes": [
    "text/plain"
  ],
  "defaultOutputModes": [
    "text/plain"
  ],
  "skill": [
    {
      "id": "ethics-utilitarian",
      "name": "Digitial Utilitarian",
      "description": "Address ethical problems and dilemmas using utilitarian approaches.",
      "tags": [
        "philosophy",
        "ethics",
        "reasoning",
        "morality",
        "moral philosophy",
        "utilitarianism",
        "hedonic calculus"
      ],
      "examples": [
        "Is it okay to cross a crosswalk against the light if there are no cars coming?"
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the most part, the card format is so intuitive that we can read through the fields and understand the purpose. In a nutshell, though, the card describes what this endpoint can do, and exposes one or more &lt;code&gt;skills&lt;/code&gt;. When a client issues a request against this A2A service, it can use the skill ID to inform the endpoint what specific task it is asking the agent to perform. In the example above, one skill is exposed: A utilitarian ethical reasoner. (Utilitarianism is a philosophical system of ethics that determines what is ethical based on how much pleasure or pain is caused by an action.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Inter-agent Interaction with JSON-RPC
&lt;/h3&gt;

&lt;p&gt;Thanks to the card, humans and agents (and even tools) can all understand what agents we have made available, and what they do. Thanks to the A2A interaction protocol, it is also clear how to interact with our agents.&lt;/p&gt;

&lt;p&gt;The JSON-RPC 2.0 specification defines a remote procedure call (RPC) mechanism that defines how a client can request that a service run a function and return the result to the client. We won’t go into this protocol in any detail, but there is one thing specific to A2A that we should cover: The multiple modes of interaction.&lt;/p&gt;

&lt;p&gt;The agent card can describe its capabilities. In the example above, there is a section that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"capabilities": {
  "streaming": false,
  "pushNotifications": false
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;speaking practically, it means that as we write our agents, we have different options for how to construct the interaction between two agents.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asynchronous Sessions: Send a request, get a response that includes a session ID. For a follow-up message in the same context, send the session ID. This is a default feature. All A2A servers are expected to implement it.&lt;/li&gt;
&lt;li&gt;Streamed Sessions: Open a stream and send a series of messages back and forth. This technique uses HTTP SSE (Server Sent Events) to keep a stream open and allow the server to initiate events. By setting the &lt;code&gt;streaming&lt;/code&gt; capability to &lt;code&gt;true&lt;/code&gt;, we can indicate that we support it.&lt;/li&gt;
&lt;li&gt;Push Notifications: Send a request that includes a notification URL. When the agent is done processing the request, notify via the notification URL. This uses webhooks as a callback mechanism for an agent. We can indicate support for this in the agent card by setting the &lt;code&gt;pushNotifications&lt;/code&gt; capability to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that the very simplest case, a one-shot question-and-answer, is just a simple use of asynchronous sessions. We’ll build one of those in the next section.&lt;/p&gt;

&lt;p&gt;How do you choose which one to use in any given set of circumstances?&lt;/p&gt;

&lt;p&gt;Streamed sessions use a long-running network connection to send multiple messages between the client and the server agent. In most cases, asynchronous sessions are better (and I’ll describe why in a moment). But one cases where streamed sessions works very well is when the client side of the communication is not another agent, but is a human who is impatient and wants to see at least the beginning of results as soon as they are available. You can envision the way that many AI-based chats stream back tokens to emulate a user typing on a keyboard.&lt;/p&gt;

&lt;p&gt;Streamed sessions are less resource efficient because they consume network and compute resources on both the client and server even when one or both sides are just waiting. SSE logic adds more complexity to application code, and often do no good if the client-side is another agent or tool. There is no reason to keep a network connection open and compute resources allocated while waiting for a client agent to run returned data through its own model, or even contact other agents. This is wasteful in an expensive way.&lt;/p&gt;

&lt;p&gt;A2A’s asynchronous mode is a better fit in most cases. In this case, a request is sent to the agent, which sends back a response and then closes the connection. If the client agent then needs to make a follow-up request, it opens a new connection and sends a new request. By sending the session ID provided by the server agent in the last interaction, the client clearly indicates that this is the continuation of an existing session rather than a new session. This mode fits better with the agent-to-agent modeling we’d expect, where a client agent would receive a response, process it through its own logic, and possibly (perhaps seconds, minutes, or even hours later) issue a follow-up request.&lt;/p&gt;

&lt;p&gt;This points to an emerging trend. As agent-to-agent interactions become more popular, we will stop looking at sessions as short-lived (as they are in the human-to-agent chat apps today). Instead, they will have durations more like email exchanges, which may last months or longer, but be intermittent. Building with an asynchronous mode just makes more sense.&lt;/p&gt;

&lt;p&gt;But there is another option that works really well if the task at hand will take the server agent longer than a couple of minutes. And that’s the push notification method. For example, generating videos or large images may take a non-trivial amount of time. And, again, if this agent-to-agent thing really takes off, it may take a long time for the server agent to find and talk to other agents that will help it complete its task. In these cases, supplying a callback webhook means the client- and server-side agents can disconnect (and free up network resources) while the server-side agents continues to work. Then the server-side agent can inform the client via webhook that the result is ready.&lt;/p&gt;

&lt;p&gt;Now that we have a solid understanding of how A2A works, let’s take a look at an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Virtual Ethicist Code
&lt;/h2&gt;

&lt;p&gt;Our agent will take a situation and provide a recommended action based on one of a few prominent moral philosophies. We’ll be using Gemini’s reasoning LLM, and here is the prompt we will send it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;You are a utilitarian moral philosopher. Announce which moral framework you are using, and then answer the question.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We’ll be writing the agent as a Spin app. To get going, we’ll use &lt;code&gt;spin new&lt;/code&gt; and then immediately do a &lt;code&gt;spin build&lt;/code&gt; to fetch all the necessary dependencies and get us started. The entire code can be found &lt;a href="https://github.com/technosophos/a2a-example/tree/main" rel="noopener noreferrer"&gt;here in GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we can edit the &lt;code&gt;src/index.ts&lt;/code&gt; file. We’ll focus on just two key functions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;taskUtilitarian&lt;/code&gt; is the main task for our agent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;doGeminiInference&lt;/code&gt; fulfills the task using Google’s Gemini
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The main task handler for each request
async function taskUtilitarian(id: string, params: any): Promise&amp;lt;Response&amp;gt; {

  // Get the question from the client's request
  let question = params.message.parts[0].text;

  // Create a task object that returns a one-shot inference
  let task = new Task();
  task.id = id;
  task.sessionId = generateSessionId();
  task.status = new TaskStatus();
  task.status.state = "completed";
  task.status.message = {
    role: "agent",
    parts: [
      {
        type: "text",
        // Ask the question to Gemini and wait for the answer
        text: await doGeminiInference(question),
      },
    ],
  };

  // Build the JSON-RPC response envelope
  let envelope = {
    jsonrpc: "2.0",
    id: id,
    result: task,
  };

  // Send the entire response back to the client
  let res = new Response(JSON.stringify(envelope));
  res.headers.set("content-type", "application/json");
  return res;
}

// Helper that uses Google Gemini to do the inference
async function doGeminiInference(question: string): Promise&amp;lt;string&amp;gt; {
  let inference = await gemini.models.generateContent({
    model: "gemini-2.0-flash",
    contents: `You are a utilitarian moral philosopher. Announce which moral framework you are using, and then answer the question.

    ${question}`,
  });
  return Promise.resolve(inference.text || "Some philosophers choose silence.");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The snippet above shows the main agent, which runs a task on behalf of the client and then returns the result. We’re using the Gemini 2.0 Flash model, which is fast and efficient, and does a good job of handling this type of request.&lt;/p&gt;

&lt;p&gt;One really cool thing about A2A is that the details of how we do the inference are opaque to the end user. If we discovered that, say, LLaMa did a better job of answering ethical questions, we could swap all of this out without so much as inconveniencing clients. In a sense, this is part of what makes A2A a compelling technology: One agent can learn how to interact with another agent, but without needing detailed information about how that agent is getting its work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Should Agents Run?
&lt;/h2&gt;

&lt;p&gt;The agent we built above is a Spin app. Spin applications can run in a variety of locations, from your local system to Kubernetes clusters to the Akamai edge. What makes the most sense?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the results are cacheable, then put the application on the edge, as that will ensure that the agent runs as close as possible to the requesting client (be it another agent or a human).&lt;/li&gt;
&lt;li&gt;If you are running your own models, run the app as close to your GPUs as possible. So, for example, if you are using Civo GPUs to host a model, run the code in Civo’s cloud. (And if you are running edge GPUs, like with Akamai Cloud GPUs, this gives you the edge advantage even for self-hosted models)&lt;/li&gt;
&lt;li&gt;If you are using an AI service like Google Gemini, running as close to the user is likely a good move, since that will make use of the nearest GPU to the client. So once again, running at edge is best.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the Gemini-based example above, I deployed to an Akamai endpoint, and then used Google’s demo A2A client to connect. Here’s what it looks like in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgysu854gv1hnrpch4gh9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgysu854gv1hnrpch4gh9.png" alt="Lengthy output from the digital ethics LLM" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The response from the agent is detailed, providing a reason and then a “final verdict” for the prescribed action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;MCP is a protocol for exposing tools to agents. But A2A is different. Its purpose is to standardize a way for one agent to call to another agent. That requires two basic features: The ability to &lt;em&gt;discover&lt;/em&gt; other agents, and the ability to interact with discovered agents. In A2A, the first is provided by JSON files in a simple agent card format. The second is provided via the existing JSON-RPC specification plus a few refinements for handling the longer interactions required by AI processing.&lt;/p&gt;

&lt;p&gt;A2A is still lifting off, and it may be a little while before we see agents actually discovering and making use of other agents. But the foundation for this kind of interaction is laid by this new specification.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>ai</category>
      <category>mcp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Unlocking Otel in WebAssembly - Wasm I/O 2025</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Thu, 03 Jul 2025 22:29:19 +0000</pubDate>
      <link>https://forem.com/fermyon/unlocking-otel-in-webassembly-wasm-io-2025-1m8n</link>
      <guid>https://forem.com/fermyon/unlocking-otel-in-webassembly-wasm-io-2025-1m8n</guid>
      <description>&lt;p&gt;As WebAssembly continues its march into production environments, the need for robust observability becomes increasingly critical. At Wasm I/O 2025, Caleb gave &lt;a href="https://2025.wasm.io/slides/unlocking-observability-in-webassembly-with-opentelemetry-wasmio25.pdf" rel="noopener noreferrer"&gt;an insightful talk&lt;/a&gt; on how OpenTelemetry is solving the observability puzzle for WebAssembly apps.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/iKh8YlJh618"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=iKh8YlJh618" rel="noopener noreferrer"&gt;YouTube Recording: Unlocking Observability in WebAssembly with OpenTelemetry by Caleb Schoepp @ Wasm I/O 2025&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge: Observing the Unobservable
&lt;/h3&gt;

&lt;p&gt;WebAssembly’s sandboxed nature, whilst providing excellent security and portability, creates unique challenges for observability. Traditional monitoring approaches often fall short when dealing with the host-guest architecture that defines WebAssembly apps.&lt;/p&gt;

&lt;p&gt;Caleb outlines three key places where telemetry can be collected in WebAssembly systems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Host-Guest Telemetry - Observing interactions between the WebAssembly runtime and components&lt;/li&gt;
&lt;li&gt;Inter-Guest Telemetry - Tracking communication between different WebAssembly components&lt;/li&gt;
&lt;li&gt;Intra-Guest Telemetry - Emitting traces from within WebAssembly components themselves&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Auto-Instrumentation: The Low-Hanging Fruit
&lt;/h3&gt;

&lt;p&gt;One of WebAssembly’s superpowers is the ability to provide automatic instrumentation without much developer effort. Caleb shows this using &lt;a href="https://spinframework.dev/" rel="noopener noreferrer"&gt;Spin&lt;/a&gt;, the serverless WebAssembly framework for developing apps. He shows how a simple Rust component automatically generates rich tracing data.&lt;/p&gt;

&lt;p&gt;Trace data includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP request handling&lt;/li&gt;
&lt;li&gt;Component execution spans&lt;/li&gt;
&lt;li&gt;Host resource interactions (like key-value store operations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This auto-instrumentation is cross-language and requires zero code changes - you simply get observability “for free” by running your components in an instrumented runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond Auto-Instrumentation: Custom Tracing
&lt;/h3&gt;

&lt;p&gt;While auto-instrumentation provides valuable baseline visibility, production apps need custom tracing that reflects business logic and application-specific workflows. This is where OpenTelemetry’s standard APIs shine.&lt;/p&gt;

&lt;p&gt;The challenge? Getting OpenTelemetry to work seamlessly within WebAssembly’s constrained environment.&lt;/p&gt;

&lt;p&gt;Caleb demonstrates two approaches:&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: HTTP Exporter with WASI-HTTP
&lt;/h2&gt;

&lt;p&gt;Using familiar OpenTelemetry SDKs with an HTTP exporter backed by WASI-HTTP. This approach feels familiar to developers but has limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Difficult to compile OpenTelemetry libraries to WebAssembly&lt;/li&gt;
&lt;li&gt;Can’t access parent trace context in non-HTTP components&lt;/li&gt;
&lt;li&gt;Unable to properly associate host operations with guest spans&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach 2: WASI-OTel Processor
&lt;/h2&gt;

&lt;p&gt;A more sophisticated approach using a special processor backed by the new WASI-OTel proposal. This enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper parent-child relationships between host and guest spans&lt;/li&gt;
&lt;li&gt;Full trace context propagation across the host-guest boundary&lt;/li&gt;
&lt;li&gt;Seamless integration of host operations into guest-initiated traces&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The WASI-OTel Proposal: Bridging the Gap
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/calebschoepp/wasi-otel" rel="noopener noreferrer"&gt;WASI-OTel proposal&lt;/a&gt; represents a significant step forward for WebAssembly observability. Currently in Phase 0 of the WASI process, it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardized interfaces&lt;/strong&gt; for telemetry data exchange between host and guest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Processor callbacks&lt;/strong&gt; (onStart, onEnd) that notify the host of guest span lifecycle events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context propagation&lt;/strong&gt; methods that enable proper trace parenting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The proposal is wrapped in language-specific SDKs (starting with Rust) that make it transparent for developers to use standard OpenTelemetry APIs while benefiting from WebAssembly-specific optimizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking Ahead
&lt;/h3&gt;

&lt;p&gt;This work represents the evolution from earlier efforts like WASI-Observe to a more focused, OpenTelemetry-native approach. The community’s convergence around OpenTelemetry as the observability standard drove this decision, ensuring WebAssembly applications can integrate seamlessly with existing observability infrastructure.&lt;/p&gt;

&lt;p&gt;Next steps include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advancing WASI-OTel through the WASI standardization process&lt;/li&gt;
&lt;li&gt;Expanding language support beyond Rust&lt;/li&gt;
&lt;li&gt;Adding metrics and logs support (currently focused on tracing)&lt;/li&gt;
&lt;li&gt;Gaining broader runtime support beyond Spin&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;As WebAssembly moves beyond experimental use cases into production-critical applications, observability becomes a prerequisite, not a nice-to-have. The combination of automatic instrumentation and developer-controlled custom tracing provides a compelling observability story that could accelerate WebAssembly adoption in enterprise environments. The work showcased here demonstrates that WebAssembly doesn’t have to be a black box.&lt;/p&gt;

&lt;p&gt;With the right tooling and standards, it can provide even richer observability than traditional deployment models, thanks to the runtime’s deep understanding of component interactions and resource usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fermyon/otel-plugin" rel="noopener noreferrer"&gt;Spin Otel Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spinframework.dev/v3/observing-apps" rel="noopener noreferrer"&gt;Spin Otel Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/calebschoepp/wasi-otel" rel="noopener noreferrer"&gt;WASI-OTel proposal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=iKh8YlJh618" rel="noopener noreferrer"&gt;YouTube Recording&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2025.wasm.io/slides/unlocking-observability-in-webassembly-with-opentelemetry-wasmio25.pdf" rel="noopener noreferrer"&gt;Caleb’s Slides&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>webassembly</category>
      <category>wasmio</category>
    </item>
    <item>
      <title>Why We Chose Rust For Spin</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Fri, 27 Jun 2025 15:41:53 +0000</pubDate>
      <link>https://forem.com/fermyon/why-we-chose-rust-for-spin-f6p</link>
      <guid>https://forem.com/fermyon/why-we-chose-rust-for-spin-f6p</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: Thorsten Hans&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond the Obvious: Our Journey into Rust
&lt;/h3&gt;

&lt;p&gt;When Fermyon set out to implement &lt;a href="https://spinframework.dev/" rel="noopener noreferrer"&gt;Spin&lt;/a&gt;, the open-source framework for building serverless WebAssembly applications, the decision to use Rust wasn’t just logical - it felt inevitable. Sure, we could cite the obvious characteristics of Rust, like its memory safety guarantees without garbage collection, its blazing-fast performance, or its growing ecosystem. But for us at Fermyon, these reasons were only the beginning of the story. We appreciated the depth of Rust’s philosophy and tooling, and their alignment with our goals for Spin. Here’s why we chose Rust, and how it has helped us shape Spin into the powerful tool we envisioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  It All Began With wasmtime
&lt;/h3&gt;

&lt;p&gt;Spin assists you in building and running WebAssembly (Wasm) workloads. We use &lt;a href="https://github.com/bytecodealliance/wasmtime" rel="noopener noreferrer"&gt;wasmtime&lt;/a&gt; as the WebAssembly runtime in Spin. &lt;code&gt;wasmtime&lt;/code&gt; itself is written in Rust, and this gave us a significant head start. By choosing Rust for Spin, we gained immediate interoperability with &lt;code&gt;wasmtime&lt;/code&gt; and access to its rich ecosystem of libraries and tools. This synergy allowed us to focus on crafting Spin’s remarkable developer experience and features while leveraging the stability and performance of &lt;code&gt;wasmtime&lt;/code&gt;. In other words, Rust didn’t just complement our goals—it supercharged them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Developer Experience: A Core Objective
&lt;/h2&gt;

&lt;p&gt;At Fermyon, we set ourselves an ambitious goal: to provide the best possible experience for developers working with Spin. This objective guided every decision we made, from the architecture to the implementation. Specifically, we envisioned Spin as a framework that developers could extend and personalize with ease. To achieve this, we introduced:&lt;/p&gt;

&lt;h3&gt;
  
  
  Spin Plugins
&lt;/h3&gt;

&lt;p&gt;Rust’s modularity and ergonomic design, combined with the powerful &lt;a href="https://crates.io/crates/clap" rel="noopener noreferrer"&gt;clap crate&lt;/a&gt;, allowed us to build Spin’s highly extensible Command Line Interface (CLI).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clap&lt;/code&gt; provided the foundation for designing and implementing the Spin CLI and its plugin architecture, enabling a seamless and intuitive developer experience. With Spin Plugins, developers can extend Spin’s capabilities by leveraging community-contributed plugins or even creating their own. Notably, plugins themselves can be implemented using a wide range of programming languages, with Rust and Go being the most popular choices – as of today. Looking at the source code, we can see how sub-commands provided through plugins are added to the Spin CLI using APIs provided by &lt;code&gt;clap&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Parser)]&lt;/span&gt;
&lt;span class="nd"&gt;#[clap(name&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"spin"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;version&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;version())]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SpinApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// snip&lt;/span&gt;
    &lt;span class="nd"&gt;#[clap(alias&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"n"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NewCommand&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;#[clap(alias&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"b"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildCommand&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;#[clap(alias&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"u"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="nf"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpCommand&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nd"&gt;#[clap(external_subcommand)]&lt;/span&gt;
    &lt;span class="nf"&gt;External&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;_main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c1"&gt;// snip&lt;/span&gt;
 &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;SpinApp&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;plugin_help_entries&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;subcmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="nf"&gt;.display_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plugin&lt;/span&gt;&lt;span class="py"&gt;.about&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.allow_hyphen_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="nf"&gt;.disable_help_flag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="nf"&gt;.arg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;clap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.allow_hyphen_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="nf"&gt;.multiple_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
   &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="nf"&gt;.subcommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subcmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;plugin_help_entries&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="nf"&gt;.after_help&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"* implemented via plugin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// snip&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom Spin Plugins empowers developers to tailor Spin to their unique workflows and project requirements, fostering innovation and adaptability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spin Templates
&lt;/h3&gt;

&lt;p&gt;Rust’s expressive type system and tooling enabled us to build Spin Templates, which are pre-defined scaffolds for creating Spin applications. These templates allow users to bootstrap new applications quickly and effectively. Template customization is powered by the Liquid template language and its Rust implementation provided by the &lt;a href="https://crates.io/crates/liquid" rel="noopener noreferrer"&gt;liquid crate&lt;/a&gt;, offering flexibility and ease of use.&lt;/p&gt;

&lt;p&gt;Additionally, we leverage the &lt;a href="https://crates.io/crates/dialoguer" rel="noopener noreferrer"&gt;dialoguer crate&lt;/a&gt; to render a robust and user-friendly terminal user interface (TUI), simplifying the process of collecting template parameters and enhancing the overall developer experience.&lt;/p&gt;

&lt;p&gt;The following snippet shows how &lt;code&gt;spin new&lt;/code&gt; asks users for template parameters - using either &lt;code&gt;Input&lt;/code&gt; or &lt;code&gt;Select&lt;/code&gt; provided by &lt;code&gt;dialoguer&lt;/code&gt; - when creating a new Spin application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;prompt_parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;TemplateParameter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="nf"&gt;.prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="nf"&gt;.default_value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="nf"&gt;.data_type&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;TemplateParameterDataType&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;constraints&lt;/span&gt;&lt;span class="py"&gt;.allowed_values&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ask_choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ask_free_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;parameter&lt;/span&gt;&lt;span class="nf"&gt;.validate_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid value: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Invalid value: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ask_free_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.with_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.interact_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ask_choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.with_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;default_value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.position&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="nf"&gt;.default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;selected_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="nf"&gt;.interact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;allowed_values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;selected_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Furthermore, users could roll their own templates to enhance their productivity or to ensure alignment with specific regulatory compliance requirements, tailoring the development process to their unique needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spin Factors
&lt;/h3&gt;

&lt;p&gt;Spin Factors are a core design principle and pattern within the Spin codebase. They allow users and organizations to add or customize the behavior of the Spin Runtime (Host). This flexibility is pivotal in adapting Spin to specific needs and use cases, making it not only a tool but a platform that evolves with its users. By leveraging Spin Factors, developers gain the ability to shape the runtime itself, empowering them to innovate at a higher level.&lt;/p&gt;

&lt;p&gt;For the sake of this article, let’s take a look at one of the unit tests that the team wrote for Spin Factors. The test implementation should give you a sense of Spin’s flexibility when it comes to creating a tailored runtime for running Spin Applications in restricted environments and only providing a subset of the default Spin Factors (key-value store in this instance).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(RuntimeFactors)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;TestFactors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KeyValueFactor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="nb"&gt;From&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RuntimeConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;TestFactorsRuntimeConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RuntimeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;key_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::test]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;works_when_allowed_store_is_defined&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;runtime_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;RuntimeConfig&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;runtime_config&lt;/span&gt;&lt;span class="nf"&gt;.add_store_manager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;mock_store_manager&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;factors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestFactors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;key_value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;KeyValueFactor&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;TestEnvironment&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.extend_manifest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;toml!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="py"&gt;.test&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"does-not-exist.wasm"&lt;/span&gt;
        &lt;span class="n"&gt;key_value_stores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="nf"&gt;.runtime_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;runtime_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.build_instance_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.key_value&lt;/span&gt;&lt;span class="nf"&gt;.allowed_stores&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="nf"&gt;.into_iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="py"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashSet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.key_value&lt;/span&gt;&lt;span class="nf"&gt;.open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nf"&gt;.to_owned&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nf"&gt;.is_ok&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By incorporating these features and patterns, we’ve been able to bake extensibility directly into Spin, empowering users to shape the framework rather than be constrained by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Code at Scale: How We Use Rust in the Spin Codebase
&lt;/h2&gt;

&lt;p&gt;Spin is a large-scale project, and maintaining its complexity without losing simplicity requires discipline. Thankfully, Rust’s ecosystem is not just about writing code—it’s about writing manageable, maintainable, and scalable code. Below are three examples, outlining why Rust is invaluable for building and managing codebases at scale:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Cargo Workspaces
&lt;/h3&gt;

&lt;p&gt;Cargo Workspaces are a game-changer for managing large projects. Spin is composed of multiple interdependent crates, and using workspaces allows us to organize these crates into logical units without sacrificing efficiency. Workspaces lay the foundation for increasing the end-to-end build performance, managing shared dependencies, centralized configuration management, simplifying development and reducing overhead. For example, the bespoke &lt;a href="https://github.com/spinframework/spin/tree/main/crates/plugins" rel="noopener noreferrer"&gt;Spin plugin architecture&lt;/a&gt;, &lt;a href="https://github.com/spinframework/spin/tree/main/crates/templates" rel="noopener noreferrer"&gt;Spin Templates&lt;/a&gt;, and &lt;a href="https://github.com/spinframework/spin/tree/main/crates" rel="noopener noreferrer"&gt;Spin Factors&lt;/a&gt; are managed as separate crates within a shared workspace, ensuring clean separation of concerns while maintaining cohesion.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Rust’s Strong Type System
&lt;/h3&gt;

&lt;p&gt;Rust’s strong type system, coupled with its support for traits and generics, provides a powerful foundation for building scalable and maintainable codebases like Spin. Traits enable the definition of shared behaviors across diverse components, ensuring consistency and reducing duplication, while generics allow developers to write highly reusable abstractions that are both type-safe and performant. These features significantly enhance the robustness and adaptability of the Spin codebase, simplifying the management of its complexity as it grows. By leveraging Rust’s expressive type system, Spin achieves a higher level of reliability and clarity, making it easier to scale and maintain without compromising on speed or safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Powerful, Robust &amp;amp; Efficient Toolchain
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://doc.rust-lang.org/cargo/" rel="noopener noreferrer"&gt;cargo&lt;/a&gt;, &lt;a href="https://github.com/rust-lang/rustfmt" rel="noopener noreferrer"&gt;rustfmt&lt;/a&gt;, &lt;a href="https://github.com/rust-lang/rust-clippy" rel="noopener noreferrer"&gt;clippy&lt;/a&gt;, &lt;a href="https://rust-analyzer.github.io/" rel="noopener noreferrer"&gt;rust-analyzer&lt;/a&gt;, and Rust’s robust unit testing capabilities together form a powerful ecosystem for managing large-scale projects like Spin.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cargo&lt;/code&gt; streamlines dependency management and builds, while &lt;code&gt;rustfmt&lt;/code&gt; ensures consistent code formatting across the project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clippy&lt;/code&gt; offers insightful linting capabilities, guiding developers toward best practices and preventing common programming pitfalls.&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;code&gt;rust-analyzer&lt;/code&gt; enhances development workflows through intelligent code navigation and suggestions, significantly accelerating productivity.&lt;/p&gt;

&lt;p&gt;Combined with Rust’s native support for unit testing, these tools collectively empower developers to maintain clarity, reliability, and scalability in even the most complex codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust Matters
&lt;/h2&gt;

&lt;p&gt;Rust isn’t just a programming language; it’s a mindset. Its emphasis on safety, performance, and maintainability resonates deeply with our goals for Spin. By choosing Rust, we’ve been able to deliver a toolchain that is fast, reliable, and extensible—all without compromising on developer experience.&lt;/p&gt;

&lt;p&gt;Rust allows us release new features at an incredible pace. As Spin continues to grow, we remain confident that Rust will scale with us, supporting our mission to redefine serverless development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For those exploring Rust or questioning why others choose it, our experience with Spin offers a compelling case. Beyond its technical merits, Rust provides a foundation for creativity and scalability that few other languages can match. At Fermyon, we didn’t just choose Rust for what it is —we chose it for what it empowers us to build.&lt;/p&gt;

&lt;p&gt;If you’re a developer intrigued by Rust, consider diving into the Spin codebase. You might discover that Rust isn’t just a tool for building software — it’s a framework for building possibilities.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>webassembly</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI at The Edge With Fermyon Wasm Functions</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Fri, 13 Jun 2025 16:20:32 +0000</pubDate>
      <link>https://forem.com/fermyon/ai-at-the-edge-with-fermyon-wasm-functions-ca1</link>
      <guid>https://forem.com/fermyon/ai-at-the-edge-with-fermyon-wasm-functions-ca1</guid>
      <description>&lt;p&gt;&lt;strong&gt;By: MacKenzie Adam&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s start off this post with a short exercise in imagination.&lt;/p&gt;

&lt;p&gt;It’s your first day at KubeCon. The smell of drip coffee and baked goods greets you, along with hundreds of sponsor booths lining the hallway, staffed with people eager to teach you how their technology can help you build more secure, efficient, and cost-effective software.&lt;/p&gt;

&lt;p&gt;It can be an intimidating task, deciding how to split your time between perusing the conference floor, attending the talks most relevant to your interests, and making all those coffee chats you lined up before the event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe77hmalw65v071htazck.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe77hmalw65v071htazck.gif" alt="kubecon schedule gif" width="600" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To take one task off your plate, we built &lt;strong&gt;KubeAround&lt;/strong&gt;, an AI-powered scheduling assistant for conferences. Instead of manually sifting through the talk schedule (there were &lt;a href="https://www.cncf.io/announcements/2025/01/15/kubecon-cloudnativecon-europe-2025-schedule-is-live-brimming-with-innovative-and-insightful-cloud-native-content/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;229 sessions at KubeCon + CloudNativeCon Europe 2025&lt;/a&gt;!), you can simply ask KubeAround (via voice or text) to find the best sessions for your interests and availability. And it responds in milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9k1guya8ys5j07w624k.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9k1guya8ys5j07w624k.gif" alt="Gif of KubeAround" width="760" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Try it live: &lt;a href="https://be68c835-d0d6-427c-97d3-d8037da5722e.aka.fermyon.tech/" rel="noopener noreferrer"&gt;KubeCon Japan Demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Under the hood, KubeAround is composed of several key parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend, Edge-API &amp;amp; Key-Value Store deployed globally using Fermyon Wasm Functions&lt;/li&gt;
&lt;li&gt;A centralized API implementing the Retrieval-Augmented Generation (RAG) pattern along with a central cache hosted on Akamai App Platform via open-source &lt;a href="https://www.spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Multiple LLMs hosted on GPU-backed Linode instances&lt;/li&gt;
&lt;li&gt;A vector database powered by &lt;a href="https://turso.tech/vector" rel="noopener noreferrer"&gt;Turso&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Embedding Models powered by Fermyon Cloud to compute vectors
Let’s dive into how these services work together to deliver a fast, secure, and delightful experience for both users and developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Globally Distributed Frontend, Edge-API and Key-Value Store
&lt;/h2&gt;

&lt;p&gt;The frontend and Edge API are bundled into a single Spin application and deployed globally via the Fermyon Wasm Functions &lt;a href="https://dev.tospin%20aka%20plugin"&gt;spin aka plugin&lt;/a&gt;. This gives us low-latency compute and fast content delivery at the edge. Leveraging the NoOps Key-Value Store provided by Fermyon Wasm Functions, we can generate recommendations for known questions in no-time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Component
&lt;/h3&gt;

&lt;p&gt;We chose Vue.js for its lightweight footprint and reactive data binding, which helps keep the UI fast and responsive. KubeAround’s frontend has a few responsibilites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture user input (either voice or text)&lt;/li&gt;
&lt;li&gt;Send user input to Edge-API&lt;/li&gt;
&lt;li&gt;Display recommendations streamed from the Edge-API&lt;/li&gt;
&lt;li&gt;Render a simple toggle to A/B test different Large Language Models (LLMs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Static assets are served via Spin’s &lt;a href="https://developer.fermyon.com/hub/preview/template_spin_fileserver" rel="noopener noreferrer"&gt;static file server template&lt;/a&gt; for lightning-fast load times. Here’s a snippet from the application manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[trigger.http]]
route = "/..."
component = "frontend"

[component.frontend]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.3.0/spin_static_fs.wasm", digest = "sha256:ef88708817e107bf49985c7cefe4dd1f199bf26f6727819183d5c996baa3d148" }
files = [{ source = "static", destination = "/" }]

# Build the Vue Frontend when `spin build` is executed
[component.frontend.build]
command = "npm install &amp;amp;&amp;amp; npm run build &amp;amp;&amp;amp; touch ../static/.gitkeep"
workdir = "frontend"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spin’s capability-based security model ensures this component can only access the “files” directory, giving us a secure-by-default frontend.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you’re interesting hosting a static site with Spin, we have plenty of starter templates available on &lt;a href="https://developer.fermyon.com/hub" rel="noopener noreferrer"&gt;Spin Hub&lt;/a&gt; including Jekyll, Zola, Angular, Tera, NextJS, 11ty, Docuusaus, Quik, Hugo, Dioxus.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Edge-API component
&lt;/h3&gt;

&lt;p&gt;The Edge-API hanldes communication between the frontend and backend services running on Akamai’s App Platform. This component is implemented in Typescript for ease of development, but could have just as easily have been written a performance sensitive language such as Rust thanks to the polyglot capabilities of WebAssembly. The main responsibilities of our Edge-API are to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translate voice input to text by sending it to Open-Whisper running on Linode GPU for transcription&lt;/li&gt;
&lt;li&gt;Take user queries and model selection, check to see if we have a response in the &lt;a href="https://developer.fermyon.com/wasm-functions/using-key-value-store" rel="noopener noreferrer"&gt;Fermyon Wasm Functions Key Value Store&lt;/a&gt;; otherwise forward to our Central API for processing&lt;/li&gt;
&lt;li&gt;Use Response teeing to cache new answers in the Edge Key-Value store and stream them to the frontend in parallel
Within the application manifest, you can see our Edge-API has been granted the necessary capabilities required to complete these responsibilites:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[trigger.http]]
route = "/api/..."
component = "api"

[component.api]
source = "api/dist/api.wasm"
# We use Key Value Store to cache recurring questions
key_value_stores = ["default"]
# Allow outbound connectivity to the central api running on Akamai App Platform
allowed_outbound_hosts = ["{{ central_api_endpoint }}"]

[component.api.build]
command = "npm install &amp;amp;&amp;amp; npm run build"
workdir = "api"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the frontend component, our Edge-API component must have access to a Fermyon Wasm Functions Key Value store to pull in cached user queries and answers to deliver latency-sentitive data to our users. In the event we have a cache miss, it has permission to make outbound calls to the central-API for updated response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Locally and Deploy Globally
&lt;/h3&gt;

&lt;p&gt;KubeAround has been designed as a modular system, meaning we can independently test our frontend and edge-API components. To do so, we’ll run the &lt;code&gt;spin build&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ spin build
Building component frontend with `npm install &amp;amp;&amp;amp; npm run build &amp;amp;&amp;amp; touch ../static/.gitkeep`
Working directory: "./frontend"
&amp;lt; ... &amp;gt; 
loaded configuration for: [ '@fermyon/spin-sdk' ]
asset bundle.js 32.7 KiB [emitted] [javascript module] (name: main)
orphan modules 44.3 KiB [orphan] 29 modules
runtime modules 396 bytes 2 modules
./src/index.ts + 11 modules 31.6 KiB [not cacheable] [built] [code generated]
webpack 5.98.0 compiled successfully in 633 ms
Using user-provided wit in: /PATH/knitwit
Component successfully written.
Finished building all Spin components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then serve it locally with &lt;code&gt;spin up&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ spin up
Logging component stdio to ".spin/logs/"
Storing default key-value data to ".spin/sqlite_key_value.db".
Preparing Wasm modules is taking a few seconds...

Serving http://127.0.0.1:3000
Available Routes:
  api: http://127.0.0.1:3000/api (wildcard)
  frontend: http://127.0.0.1:3000 (wildcard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A quick visit to &lt;code&gt;localhost:3000&lt;/code&gt; shows a local copy of our frontend and edge-API running on my machine:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvdv21iqlwcossqsvcsi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvdv21iqlwcossqsvcsi.png" alt="A screenshot of nabAround" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we’re ready to move to production, we’ll deploy using the &lt;code&gt;spin aka&lt;/code&gt; plugin. When we run the &lt;code&gt;spin aka deploy&lt;/code&gt; command, Fermyon Wasm Functions will package our application, store it in a managed OCI registry, and distribute it across all available regions on our behalf.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr543ghmhvjcbt1kzcusv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr543ghmhvjcbt1kzcusv.gif" alt="fwf deploy gif" width="400" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want to integrate with an existing Akamai property? Check out our &lt;a href="https://developer.fermyon.com/wasm-functions/property-manager-integration" rel="noopener noreferrer"&gt;Property Manager guide&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Centralized-API
&lt;/h2&gt;

&lt;p&gt;Now we’re going to get into how the &lt;del&gt;sausage&lt;/del&gt; session recommendations are made. The centralized-API turns queries into personalized schedule recommendations, leveraging a multi-step process as a Retrevial Augment Generation (RAG) service&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A RAG service combines information retrieval with generative models by fetching relevant documents from a data source and using them to ground and enhance the output of an LLM&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Embed sanitized user query using &lt;code&gt;minilm&lt;/code&gt; on Fermyon Cloud GPUs&lt;/li&gt;
&lt;li&gt;Cache sanitized user query embeddings in Valkey for faster future lookups&lt;/li&gt;
&lt;li&gt;Query vector DB (Turso) for the most relevant conference sessions&lt;/li&gt;
&lt;li&gt;Construct a prompt using retrieved results, model-specific instructions, and user preferences&lt;/li&gt;
&lt;li&gt;Send to LLM (Llama 3.2 or DeepSeek on Linode GPUs) for a response&lt;/li&gt;
&lt;li&gt;Stream response back to the Edge API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our RAG service is implemented as a Spin application, running on Akamai’s App Platform thanks to &lt;a href="https://www.spinkube.dev/" rel="noopener noreferrer"&gt;SpinKube&lt;/a&gt; (an OSS project that allows you to run your Spin applications alongside traditional containerized applications on your Kubernetes cluster).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here we can see the portability benefits in action for Spin. We’re continue to use the same format of application manifest, despite this Spin application running on Kubernetes rather than Fermyon Wasm Function’s managed platform.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A key design principle in this demo is multi-layered caching to improve responsiveness. For instance, the Central API layer includes a Valkey-backed key-value store to prevent repeated queries from being vectorized repeated queries quickly, even across different regions.&lt;/p&gt;

&lt;p&gt;With the service’s responsibilties in mind, we can see this application has by far the largest reach as it must connect to our LLMs responsibily for generating user-facing recommendations, LLMs for generated embeddings, vector databases, key-value stores for caches. There are also serveral variables used to adjust the models output, such as similarity search limits and temperature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[[trigger.http]]
route = "/..."
component = "api"

[component.api]
source = "dist/api.wasm"
exclude_files = ["**/node_modules"]
allowed_outbound_hosts = [
    "{{ llama_endpoint }}",
    "{{ alternative_model_endpoint }}",
]
# Use Fermyon Cloud to compute embeddings
ai_models = ["all-minilm-l6-v2"]

# Documents (sessions) are stored in a sqlite running on TursoDB
# See runtime configuration file
sqlite_databases = ["default"]

# Embeddings for questions are cached in key value store
# See runtime configuration file
key_value_stores = ["default"]

[component.api.variables]
llama_endpoint = "{{ llama_endpoint }}"
llama_identifier = "{{ llama_identifier }}"
alternative_model_endpoint = "{{ alternative_model_endpoint }}"
alternative_model_identifier = "{{ alternative_model_identifier }}"
similarity_search_limit = "{{ similarity_search_limit }}"
temperature = "{{ temperature }}"

[component.api.build]
command = ["npm install", "npm run build"]
watch = ["src/**/*.ts"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;We’ve covered a lot of ground together today—maybe not quite as many steps as you’d take during a week at the KubeCon conference, but close! KubeAround has shown us how distributed edge functions and caches make AI inferencing at the edge not just practical, but surprisingly powerful. For computationally intensive tasks that need to be colocated with a data source (what we affectionately call “heavy-weight functions”), we’ve learned how to host these workloads on Kubernetes clusters (such as the managed ones on &lt;a href="https://techdocs.akamai.com/cloud-computing/docs/getting-started-with-akamai-application-platform" rel="noopener noreferrer"&gt;Akamai App Platform&lt;/a&gt;) using SpinKube. With &lt;a href="https://www.linode.com/products/gpu/" rel="noopener noreferrer"&gt;Akamai Cloud GPUs&lt;/a&gt;, deploying your preferred LLM is straightforward—in this demo alone, we ran three different LLMs handling everything from voice-to-text transcription to natural language processing.&lt;/p&gt;

&lt;p&gt;If you’re planning on attending KubeCon Japan, swing by the Akamai Booth to see KubeAround in action. Otherwise, we’re around in &lt;a href="https://discord.gg/AAFNfS7NGf" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; for all of your Fermyon Wasm Functions questions!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kubernetes</category>
      <category>webdev</category>
      <category>kubecon</category>
    </item>
    <item>
      <title>Rawkode's Hands-on Intro to Fermyon Wasm Functions</title>
      <dc:creator>Jasmine Mae</dc:creator>
      <pubDate>Mon, 09 Jun 2025 01:08:42 +0000</pubDate>
      <link>https://forem.com/fermyon/rawkodes-hands-on-intro-to-fermyon-wasm-functions-1dca</link>
      <guid>https://forem.com/fermyon/rawkodes-hands-on-intro-to-fermyon-wasm-functions-1dca</guid>
      <description>&lt;p&gt;Recently, Rawkode Academy &lt;a href="https://rawkode.academy/watch/hands-on-introduction-to-fermyon-wasm-functions" rel="noopener noreferrer"&gt;recorded a deep dive&lt;/a&gt; with Fermyon Wasm Functions - where host David Flanagan and Thorsten Hans (of Fermyon) provide a hands-on session exploring the new serverless offering.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/q5FLoCT3FXk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.fermyon.com/wasm-functions" rel="noopener noreferrer"&gt;Fermyon Wasm Functions&lt;/a&gt; is a fully managed hosted service built on top of Akamai’s globally connected network. It takes the proven architecture of running Spin apps in a dense and available fashion and distributes them across multiple regions worldwide. This means your applications are always available, and requests are automatically routed to the closest data center based on the user’s location.&lt;/p&gt;

&lt;p&gt;During the &lt;a href="https://rawkode.academy/" rel="noopener noreferrer"&gt;Rawkode Academy&lt;/a&gt; session, David and Thorsten discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Benefits&lt;/li&gt;
&lt;li&gt;Wasm Functions Setup&lt;/li&gt;
&lt;li&gt;Demoing app scenarios&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Advanced Features&lt;/li&gt;
&lt;li&gt;Use Cases and Examples&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Benefits of Fermyon Wasm Functions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lightning-Fast Cold Starts
&lt;/h3&gt;

&lt;p&gt;While traditional containers measure startup time in hundreds of milliseconds, WebAssembly applications measure startup in &lt;strong&gt;nanoseconds&lt;/strong&gt;. Combined with Akamai’s global network, this delivers consistently low latency regardless of user location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Distribution Made Simple
&lt;/h3&gt;

&lt;p&gt;Instead of manually selecting regions and managing deployments, Fermyon Wasm Functions automatically distributes your application globally. Developers can focus on building great applications rather than infrastructure management.&lt;/p&gt;

&lt;h3&gt;
  
  
  Familiar Developer Experience
&lt;/h3&gt;

&lt;p&gt;The service extends the existing Spin CLI with an &lt;code&gt;aka&lt;/code&gt; plugin, maintaining the familiar workflow developers already know while adding global deployment capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites:
&lt;/h3&gt;

&lt;p&gt;To get started with Fermyon Wasm Functions, you’ll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spin CLI (&lt;a href="https://spinframework.dev/v3/install" rel="noopener noreferrer"&gt;version 3.2.0 or later&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spin &lt;code&gt;aka&lt;/code&gt; plugin to extend your Spin CLI
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin plugin install aka
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Fermyon Wasm Functions uses GitHub for authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This initiates a device code flow where you’ll authenticate with your GitHub account and grant the CLI permissions to interact with Fermyon Wasm Functions on your behalf.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your First Application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating a New Project
&lt;/h3&gt;

&lt;p&gt;Fermyon has updated their templates to provide better developer experience. For TypeScript projects, you can now choose between different HTTP routers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin new http-ts hello-fermyon-wasm-functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new templates support popular frameworks like &lt;strong&gt;Hono&lt;/strong&gt;, which provides an excellent API for building HTTP applications with clean middleware support.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Demos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Simple Hello World&lt;/strong&gt;&lt;br&gt;
A basic application that responds with personalized greetings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.get('/', (c) =&amp;gt; c.text('Hello Spin'))
app.get('/hello/:name', (c) =&amp;gt; c.text(`Hello ${c.req.param('name')}`))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. A/B Testing with Key-Value Store&lt;/strong&gt;&lt;br&gt;
A more advanced example implementing A/B testing using Fermyon’s globally distributed key-value store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Track request count
const counter = await store.getJson(HITS_KEY) || { count: 0 }
counter.count++
await store.setJson(HITS_KEY, counter)

// Return different content based on even/odd requests
if (counter.count % 2 === 0) {
    return c.html('&amp;lt;h1&amp;gt;Even&amp;lt;/h1&amp;gt;')
} else {
    return c.html('&amp;lt;h1&amp;gt;Odd&amp;lt;/h1&amp;gt;')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Deploying to Fermyon Wasm Functions is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates an OCI artifact from your Spin application&lt;/li&gt;
&lt;li&gt;Pushes it to Fermyon’s registry&lt;/li&gt;
&lt;li&gt;Deploys it across all regions&lt;/li&gt;
&lt;li&gt;Provides a generated subdomain with HTTPS certificate&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Advanced Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monitoring and Observability
&lt;/h3&gt;

&lt;p&gt;The aka plugin provides several commands for monitoring your applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spin aka apps list&lt;/code&gt; - View all deployed applications&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spin aka apps info [app-name]&lt;/code&gt; - Get detailed application information&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spin aka logs [app-name]&lt;/code&gt; - View application logs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cron Jobs (Experimental)
&lt;/h3&gt;

&lt;p&gt;Fermyon Wasm Functions supports scheduled jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka cron create --route / --schedule "* * * * *" my-cron-job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CDN Integration
&lt;/h3&gt;

&lt;p&gt;Applications automatically benefit from Akamai’s CDN. You can add cache control headers to enable caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;response.headers.set('Cache-Control', 'max-age=10')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration Management
&lt;/h3&gt;

&lt;p&gt;For sensitive configuration like API keys, use Spin’s variable system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spin aka deploy --variable secret_key=$SECRET_VALUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables are encrypted and only exposed to your specific deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Portability: A Key Advantage
&lt;/h2&gt;

&lt;p&gt;One of WebAssembly’s greatest strengths is portability. The same application you deploy to Fermyon Wasm Functions can run:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locally&lt;/strong&gt; with spin up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Kubernetes&lt;/strong&gt; using SpinKube&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On any other Spin-compatible platform&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Use Cases and Examples
&lt;/h2&gt;

&lt;p&gt;Fermyon provides a comprehensive &lt;a href="https://github.com/fermyon/fwf-examples" rel="noopener noreferrer"&gt;repository of examples&lt;/a&gt; showcasing various use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A/B Testing&lt;/strong&gt; - Dynamic content serving based on user segments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic Filtering&lt;/strong&gt; - Request filtering and routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geo-blocking&lt;/strong&gt; - Restricting access based on user location&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Header Modification&lt;/strong&gt; - Dynamic header manipulation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gated Access&lt;/strong&gt; - Time-based access control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples demonstrate how edge computing with WebAssembly enables new architectural patterns that weren’t practical with traditional serverless platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The familiar developer experience, combined with powerful features like global key-value storage, cron jobs, and seamless CDN integration, makes it an compelling choice for building modern, high-performance web applications.&lt;/p&gt;

&lt;p&gt;Whether you’re building APIs, implementing edge logic, or creating globally distributed applications, Fermyon Wasm Functions provides the tools and infrastructure to deploy your ideas worldwide with minimal complexity and maximum performance.&lt;/p&gt;

&lt;p&gt;Ready to get started? &lt;a href="https://fibsu0jcu2g.typeform.com/fwf-preview" rel="noopener noreferrer"&gt;Request access&lt;/a&gt; to Fermyon Wasm Functions and join the future of serverless computing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs: &lt;a href="https://developer.fermyon.com/wasm-functions" rel="noopener noreferrer"&gt;developer.fermyon.com/wasm-functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Examples Repository: &lt;a href="https://github.com/fermyon/fwf-examples" rel="noopener noreferrer"&gt;github.com/fermyon/fwf-examples&lt;/a&gt; community examples and tutorials&lt;/li&gt;
&lt;li&gt;Spin: &lt;a href="https://spinframework.dev/" rel="noopener noreferrer"&gt;spinframework.dev&lt;/a&gt; for the open-source Spin project&lt;/li&gt;
&lt;li&gt;Hands-On Video: &lt;a href="https://www.youtube.com/watch?v=q5FLoCT3FXk" rel="noopener noreferrer"&gt;Watch the full demonstration&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webassembly</category>
      <category>webdev</category>
      <category>learning</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
