Skip to main content
Taylor Thomas
Taylor Thomas
Taylor Thomas
||5 min read

Running your UI on wasmCloud

One of the things we've run into as we've worked with customers and developed our own examples at Cosmonic is the need to serve UIs that are consuming services you are running inside of wasmCloud. Our own examples required you to either run the UI using npm or to run a docker image. This felt less than ideal and didn't fit with our vision of WebAssembly being the future of distributed computing.

We just released a new version of the petclinic example that demonstrates how you can bundle up a UI for your application into a single actor. Now when you start the full petclinic example, the API and UI are served from the same place

How it works

This example works by including the assets in the compiled Wasm module. This does make the module larger than normal, but has the benefit of being completely portable. If you haven't seen the petclinic example before, there is an "API gateway" actor called Clinic API that dispatches requests to the appropriate actors. In this version, we dispatch any non-API calls to the UI actor to fetch the requested assets using an actor-to-actor call. We specifically chose to make the UI a separate actor to maintain separation of concerns and keep the code easy to understand.

The UI actor uses the rust-embed crate to include the bytes in compiled module, accessible by path (as if they were on disk). Many languages have this support, so as wasmCloud continues to support more languages, this same pattern should work in any language. We use the build.rs file to run the npm command to generate static assets, and then we include the assets like so:

#[derive(RustEmbed)]
#[folder = "./dist"]
struct Asset;

Yep, that's it! The actor then receives a path from the Clinic API actor and checks if that "file" exists and returns it if it does, along with an optional MIME type. The interface we use for the actor to actor call is very straightforward. If people find it useful for creating UIs, please let us know and we can look at making it a maintained interface. You can view the source code to see the full example for the UI actor.

What is an interface?

An interface definition is a contract between actors and providers. It defines the set of operations that can be made from actor to provider. You can read more about them in the wasmCloud docs.

One other thing to note here is that this example takes advantage of the automatic streaming/chunking provided by wasmCloud. You can have some decently large files you'll need to send back (in our case, it was a CSS file). If they are large enough, the underlying lattice code will automatically chunk up the data when it sends it. This is entirely transparent to your actors and providers, which is part of the reason this is so easy. In fact, this is all that is required to send an asset back:

if let Some(file) = Asset::get(trimmed) {
    return Ok(GetAssetResponse {
        found: true,
        asset: Vec::from(file.data),
        content_type: mime_guess::from_path(trimmed)
            .first()
            .map(|m| m.to_string()),
    });
}

Other possible patterns

We wanted to call out that this is not the only pattern for serving a UI. Another possible solution here if you have large or common/shared UI assets is storing those assets in a blobstore. Your equivalent of the petclinic UI actor could then use the wasmcloud:blobstore contract (wasmCloud has an S3 implementation and an on disk implementation in progress) to fetch the assets and return them rather than embedding them

We also have many ideas for the future (possibly including embedding assets alongside the actor in a bindle) and would love to highlight any other ways of doing this that the community comes up with!

What we learned

One of the great side effects of this work was discovering several bugs in the automatic chunking in the wasmCloud host. Specifically, we found that a code path hadn't been exercised properly. Because of this, a message body that should have been empty when it was encoded to send across the wire was instead a nil, which encodes to... well... nothing.

We also learned that it is entirely possible to build a full application (from UI all the way to backend services) in wasmCloud. That might seem painfully self-evident here at the end of this post, but it is important to note that this is why we dogfood (or "drink your own champagne" as we like to say) our own project. Many of the wasmCloud maintainers also work for Cosmonic, which is powered entirely by a wasmCloud backend, and we want to use and stress test wasmCloud to make it better for everyone.

Want to try wasmCloud on a managed platform?

Get in touch for a demo
Book Now

Next Steps

Hopefully this is helpful for those of you who are building applications on wasmCloud! Please feel free to comment or share your examples of how you do this. Or, if you are new to wasmCloud, try running the example, checking out our getting started docs, or joining our Slack.

Keep up to date

Subscribe to Cosmonic for occasional communication straight to your inbox.