Fitz Unifies Server and WASM with Direct RPC

The Fitz framework has introduced a novel approach to inter-component communication, enabling WebAssembly (WASM) modules to call server-side functions as if they were local. This feature, detailed in the latest installment of the FitzLiveViews series, eliminates the need for manual plumbing typically associated with bridging client-side logic with backend services.

Traditionally, invoking server-side logic from a client-side application, especially one compiled to WASM, involves significant boilerplate. Developers must define API endpoints, write server-side handlers (e.g., in Node.js, Python, or Go), implement client-side fetch requests, and manage data serialization/deserialization, often using JSON. This process is error-prone and time-consuming, creating a distinct separation between the frontend and backend development paradigms.

Fitz's new server function capability, marked by the @rpc attribute on an async fn declaration, abstracts away this complexity. When a function is annotated with @rpc, the Fitz compiler automatically generates both the server and client code necessary for its execution. On the server, this translates to a POST /__rpc/function_name endpoint. This endpoint can securely access server-side resources like databases, authentication systems, and secrets. On the client, a corresponding stub is generated that uses a fetch API to communicate with the server. Crucially, the type definitions for the function's arguments and return values are shared between both halves, ensuring type safety across the network boundary.

The developer experience is dramatically simplified. Instead of writing separate API definitions and client-side calls, a developer simply declares an asynchronous function and annotates it with @rpc. The subsequent call from a .fitzv component looks like a standard local asynchronous function call, for instance, let u = get_user(42).await?. This unified declaration and invocation model removes the need for hand-written HTTP handlers, manual fetch calls, JSON serialization/deserialization logic, route string management, and external dependencies for RPC communication.

This capability is a significant part of Fitz's vision for a "whole stack, no plumbing" development experience. Previous parts of the FitzLiveViews series demonstrated how .fitzv components could compile to two distinct targets: server-rendered HTML via LiveViews over WebSockets, and as standalone WebAssembly SPAs. The introduction of server functions bridges the gap between these two execution environments, allowing a WASM component running in the browser to seamlessly interact with server-side logic without the usual overhead.

Bridging the WASM-Server Divide

The implications for WASM development are profound. WASM has long promised a way to run performant, portable code in the browser, often seen as a successor to JavaScript for computationally intensive tasks or for leveraging existing codebases written in languages like Rust or C++. However, integrating WASM applications with backend services has remained a friction point. Developers often resort to complex JavaScript interop layers or building REST/gRPC APIs, which reintroduces the very plumbing Fitz aims to eliminate.

Fitz's approach treats server functions as first-class citizens within the component model. This means that the same function declaration can serve both as a callable backend API and as a client-side stub. The compiler handles the nuances of network communication, serialization, and deserialization. This is akin to having a system where you declare a function, and the compiler magically understands how to make it callable from anywhere, whether that's another part of the server or a WASM module running in a separate process.

Consider a typical scenario: a user profile page rendered by a WASM component needs to fetch user data from a database. With Fitz, the developer would define a function like @rpc async fn get_user_profile(user_id: UserId) -> UserProfile. This single definition ensures that:

  • A server endpoint exists at POST /__rpc/get_user_profile that can query the database for the given UserId and return a UserProfile object.
  • The WASM component can call await get_user_profile(current_user_id). The Fitz runtime in the browser handles sending the request, receiving the response, and deserializing it into the UserProfile type.

The types UserId and UserProfile are defined once and shared, guaranteeing that the data format expected by the client matches what the server sends. This eliminates a common source of bugs where client and server schemas drift apart.

The "Whole Stack, No Plumbing" Philosophy

The @rpc feature is a direct manifestation of Fitz's core philosophy: to provide a unified, high-level abstraction over the entire web stack. This means that developers can focus on application logic rather than the intricacies of network protocols, request routing, or data marshalling.

Fitz aims to provide a seamless development experience where code written for the server can be called from the client (via WASM) and vice-versa, without the typical friction. This is particularly powerful for applications that require real-time updates or complex client-server interactions. By compiling .fitzv components to both server-side rendering (for initial load and SEO) and WASM (for interactive SPA-like behavior), Fitz offers a hybrid approach that leverages the strengths of both worlds. Server functions enhance this by providing a consistent way for the WASM part to access server-side capabilities.

The absence of external dependencies for this RPC mechanism is also a key benefit. Developers are not required to integrate third-party libraries for RPC frameworks or serialization formats. Fitz handles it all internally, ensuring a consistent and predictable developer experience. This reduction in external dependencies can also lead to smaller bundle sizes for WASM components and a more stable build process.

What remains to be seen is how this system scales with a large number of RPC functions and how effectively it handles complex data types or streaming scenarios. However, for the common case of invoking discrete server-side operations from WASM, Fitz has delivered a compelling solution that significantly reduces development friction.