September 08, 2025

Introducing wasi-grpc for Spin

Brian Hardock Brian Hardock

spin grpc wasi wasm

Introducing wasi-grpc for Spin

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.

Until Spin 3.4, 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.

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.

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 newwasi-grpc crate.

Tutorial

For this tutorial, we will be adapting the tonic helloworld example client to execute in Spin. Additionally, for testing, we will run the helloworld server to execute our component against.

Step 1: Create a new Spin app

spin new -t http-rust helloworld-client --accept-defaults

This creates a new helloworld-client directory with a Spin HTTP Rust application. Change into this directory to continue with the tutorial:

cd helloworld-client

Step 2: Setup

First, in Cargo.toml, add the dependencies for working with gRPC, Protobuf, and the wasi-grpc crate:

[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}

Next, copy the helloworld proto into your current working directory.

With that in place, we can add our build dependencies for generating code from our proto file. In your Cargo.toml add the following:

[build-dependencies]
tonic-build = { version = "0.13.1", features = ["prost"] }

And finally we can add the build.rs which will generate our gRPC client from the helloworld proto:

// In build.rs

fn main() {
    tonic_build::configure()
        .build_transport(false)
        .compile_protos(&["helloworld.proto"], &[""])
        .unwrap();
}

Let’s implement!

In src/lib.rs, let’s start by pulling in the generated code from executing our build.rs:

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

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

Next let’s pull in the wasi_grpc::WasiGrpcEndpoint type which implements tower_service::Service. This type creates a bridge between tonic and wasi-hyperium which is what wasi-grpc uses under the hood for sending outbound HTTP requests.

use wasi_grpc::WasiGrpcEndpoint;

NOTE: It was decided to use wasi-hyperium as the underlying mechanism for sending outbound requests because the spin-sdk currently has certain limitations with respect to outbound streaming.

And finally we can implement our request handler:

#[http_component]
async fn handler(_req: http::Request) -> anyhow::Result<impl IntoResponse> {
    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)
}

NOTE: For simplicity, we are using a hardcoded endpoint (“http://[::1]:50051”) to communicate with our local gRPC server that implements the GreeterService. For production applications it is recommended to use application variables to inject in the value of the address at run/deploy time.

Putting this all together, the final code in src/lib.rs looks like this:

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) -> anyhow::Result<impl IntoResponse> {
    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)
}

Let’s take it for a Spin!

As mentioned earlier, we will be using the helloworld server to execute our app against. The easiest way to run this would be to clone the tonic repository and run the helloworld server:

$ gh repo clone hyperium/tonic
$ cargo run --bin helloworld-server
...
GreeterServer listening on [::1]:50051

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

[component.helloworld-client]
...
allowed_outbound_hosts = ["http://[::1]:50051"]

Now that we have our server running and app properly configured, let’s spin up our application and send it a request to execute our new component:

$ SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE=[::1]:50051 spin up --build

NOTE: The SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE 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.

And finally, let’s send a request to our app:

$ curl localhost:3000/
Hello World!

Conclusion

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:

  • Call gRPC microservices directly without sidecars
  • Integrate with APIs built on gRPC
  • Leverage high-performance streaming patterns as first-class citizens in Spin

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 routeguide-client in the wasi-grpc repository.

If you have any questions, we’d love to help over in the Spin CNCF Slack channel. Hope to see you there!

 


🔥 Recommended Posts


Quickstart Your Serverless Apps with Spin

Get Started