2Building server-side applications with Wasm modules
This chapter introduces the essentials of building server-side applications with WebAssembly by grounding the reader in core Wasm concepts and a practical, end-to-end example. It starts from raw Wasm building blocks—linear memory and primitive numeric types—and explains how higher-level data like strings are represented with pointers and lengths. The narrative then places these fundamentals within a guest-host model, where a custom host provides resources and orchestrates execution through a runtime, establishing the patterns used by server-side Wasm applications.
The hands-on portion implements a Rust “Hello, World” guest module that prepends a greeting to an input string using explicit memory management. The host injects input into the module’s linear memory, invokes a greet function, and reads the result back, demonstrating the responsibilities on both sides of the boundary. The chapter walks through setting up Rust for the wasm32 target, compiling to a .wasm artifact, and executing it with a lightweight host backed by Wasmtime, then reflects on the pitfalls of manual memory handling and brittle assumptions when manipulating raw bytes.
To address these limits, the chapter introduces WebAssembly Interface Types (WIT) and the Canonical ABI as a standardized way to express richer, language-agnostic interfaces—strings, lists, records, enums, results, resources, and more—so developers can focus on logic rather than marshalling details. It closes by outlining a longer-running project: a smart CMS composed of interoperable Wasm components. The first step is a key-value store interface (get/set) defined in WIT, setting the stage for additional components (HTTP and ML) and eventual deployment in a container-orchestrated environment.
High-level view of our "Hello, World" application.
Representing a String with Wasm's Linear Memory and Basic Types
Illustration interactions between our "Hello, World" guest and its custom host.
The basic architecture for our Smart CMS application.
Summary
Wasm's core is formed by linear memory and four primary data types: integers and floats, each available in 32 and 64-bit sizes. Building complex data structures requires utilizing these foundational elements.
Rust provides good support for working with Wasm as it can compile directly to it.
Server-side Wasm applications need a specific execution runtime like Wasmtime, which is designed to enable interaction with system resources.
Manual management of Wasm's limited foundational datatypes is intricate and susceptible to errors, emphasizing the importance of more sophisticated abstractions, such as WebAssembly Interface Types (WIT).
WIT offers a versatile, language-agnostic type system, enabling the crafting of complex data structures and interfaces to ease the interaction between various components of a Wasm application.
FAQ
Why does the chapter use Rust to build server-side Wasm modules?Rust has the most mature toolchain for Wasm, strong safety guarantees that complement Wasm’s sandboxing, and good ergonomics for compiling to wasm32 targets. Its compiler and tooling (Cargo, rustup) make building, testing, and optimizing Wasm modules straightforward.How are strings represented in raw WebAssembly?Raw Wasm has no string type. Strings are passed as two 32-bit integers: a pointer to the start of a UTF-8 byte sequence in linear memory and the length (in bytes) of that sequence.What is Wasm linear memory?Linear memory is a contiguous, growable array of bytes. Programs read and write using explicit offsets. It’s low-level and fast, giving precise control but requiring careful management.What is the guest–host architecture used on the server?The Wasm module is the guest, running inside a host runtime that provides execution and system access. In the chapter, HelloWorldHost (backed by Wasmtime) writes input bytes into the guest’s memory, calls the guest’s greet function, and then reads the result back from memory.Why is #[no_mangle] used, and why does greet take (i32, i32)?#[no_mangle] preserves the exported function name so the host can locate greet. The signature uses (i32 ptr, i32 len) to match core Wasm’s numeric types; inside Rust, these are cast to pointer and length types for memory operations.Why is unsafe Rust needed to access Wasm memory?Creating slices from raw pointers and writing into linear memory can violate safety guarantees (e.g., out-of-bounds writes or data races). Rust requires unsafe for such operations to make the risks explicit and keep safe code sound.How do I compile the Rust library into a Wasm module?- In Cargo.toml add: [lib] crate-type = ["cdylib"]
- Install the target: rustup target add wasm32-unknown-unknown
- Build: cargo build --release --target wasm32-unknown-unknown
This produces a .wasm artifact for generic Wasm environments.How do I run the generated .wasm with the provided Docker host?Copy the .wasm near your project root, then run:
docker run --rm -v ".:/data" ghcr.io/danbugs/serverside-wasm-book-code/hello-world-host:latest /data/hello_world_guest.wasm 'YourName'
--rm cleans up the container, -v mounts your folder so the host can read the module, and the final args are the wasm path and input string.What limitations did the manual “Hello, World” reveal?Manually managing memory is error-prone and brittle: it relies on fixed layout assumptions, risks overwriting data, and doesn’t scale to complex data types. Small changes (like a longer prefix) can break expectations unless all memory math is updated consistently.What are WebAssembly Interface Types (WIT) and how do they help?WIT is an IDL that defines language-agnostic interfaces with richer types (e.g., string, lists, records). Via the Canonical ABI, it lifts/lowers values between Wasm and host without manual pointer math. For example, greet can be defined as export greet: func(name: string) -> string, letting tooling generate the glue so you focus on business logic.
pro $24.99 per month
access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!