My firs hands-on experience to learn WASM!
Introduction
For a while now, I’ve been diving into articles and books on WebAssembly, exploring its mechanics and potential. But as any developer knows, there is a massive gap between reading a specification and actually mastering it. When I truly want to understand a technology, I don’t just read — I build (to understand the mechanics truly). To move past the theory of Wasm and grasp its real-world capabilities, I decided to roll up my sleeves and build a functional, end-to-end sample (basic… 🤦♂️) application.
TL-DR; What is WebAssembly A.K.A WASM?
If you search on WASM, you’ll find this definition;
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
WebAssembly is meant to be (links and references provided at the end of this post);
- Efficient and fast
- Safe
- Open and Debuggable
- and is part of “Open Web Platform”
Some of the most and common usages of WASM
Running Existing Codebases Anywhere (Portabilit
y)
Instead of rewriting decades of proven software in JavaScript or TypeScript to make it work on the web, developers can compile their existing C, C++, Rust, or Go codebases directly to Wasm.
- Adobe Photoshop Web: Adobe didn’t rewrite Photoshop in JavaScript. They compiled their massive, 30-year-old C++ desktop codebase into WebAssembly so it could run directly in Chrome.
- Game Engines: Engines like Unity and Unreal Engine use Wasm to deploy complex desktop-grade 3D games straight to a browser tab without plugins.
Cloud-Native and Server-Side Wasm (Wasi)
With the introduction of WASI (WebAssembly System Interface), Wasm left the browser entirely. It is now a direct competitor to Docker containers in the cloud.
- Microservices: Because Wasm modules are sandboxed and start up in microseconds (compared to milliseconds or seconds for Docker containers), cloud providers like Cloudflare, Fastly, and AWS use Wasm to run serverless edge functions instantly.
- Security: Wasm operates on a strict “capability-based” security model. A server-side Wasm module cannot access the file system, network, or environment variables unless explicitly granted permission by the host.
Plugin Architecture and Extensibility
Because Wasm is a secure, language-agnostic virtual machine, many massive software platforms are adopting it as their plugin engine.
- Envoy Proxy / Istio: Use Wasm so developers can write custom networking filters in any language (Rust, Go, C++) and inject them dynamically into the proxy.
- Databases: Databases like SingleStore or LibSQL allow users to write custom user-defined functions (UDFs) in Rust or C, compiling them to Wasm to run safely right next to the data.
In summary;
| Feature | What it Means | Why it Matters |
| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------- |
| **Language Agnostic** | Write in Rust, C++, Go, Zig, AssemblyScript, etc. | We aren't forced to use JavaScript for web deployment. |
| **Sandboxed Security** | Memory-isolated environment by default. | Safe to run untrusted code on both the client and the server. |
| **Predictable Performance** | Near-native execution speed with no garbage collection pauses (usually). | Consistent performance for real-time applications. |
| **Tiny Footprint** | Binary sizes are small and modules initialize instantly. | Perfect for edge computing and IoT devices. |
Baby-Step Implementation
OK, now let’s jump into what I implemented (with the help of books and articles/books I read).
The idea is to apply it to one of my domain of activities, meaning “generative ai”. To bridge the gap between WebAssembly’s technical definition and a real-world scenario; running a Large Language Model (LLM) or a Text-to-Image model directly inside a user’s web browser is an ideal application for WebAssembly (amon other use-cases). Instead of paying for cloud-hosted GPUs (to run inference, we can use Wasm to turn a local machine into the AI engine).
For sure, there are limits also on a local machine!
The idea of the application built is a privacy-first, hybrid WebAssembly (Wasm) application demonstrating both server-side WASI isolation and client-side browser-embedded machine learning.
This project balances compute and security by processing a string across two isolated Wasm layers:
- Server-Side Wasm (Rust + WASI): Instantly scrubs and redacts high-risk Personal Identifiable Information (PII) like emails before any logs touch persistent storage.
-
Client-Side Wasm (ONNX + Transformers.js): Runs an autoregressive Generative AI language model (
Qwen1.5-0.5B) directly inside the user's browser tab to generate content summaries with zero server GPU overhead.
## 📁 Project Structure
wasm-ai-app/
├── server.js # Node.js backend runner (handles static hosting & Wasm endpoints)
├── package.json # Node.js manifest and runtime dependencies
├── redactor.wasm # Compiled Server-Side Wasm binary (Generated via Rust)
├── src/
│ └── main.rs # Server-Side Rust source code for PII redaction
└── public/ # Static assets served to the client browser
├── index.html # Frontend User Interface
└── worker.js # Client-Side Wasm execution thread (Web Worker)
Server Side Binary Code
The server side code is in Rust (my first ever rust code, and maybe the last 🙈… googling and copy/pasting 😎).
use std::io::{self, Read, Write};
fn main() {
// 1. Read input text passed into the Wasm standard input
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).unwrap();
// 2. Simple fallback replacement logic for emails (avoiding external crate overhead for this demo)
let mut safe_text = buffer.clone();
if let Some(_start) = buffer.find('@') {
// Find rough boundaries of an email address to redact it
let words: Vec<&str> = buffer.split_whitespace().collect();
for word in words {
if word.contains('@') {
safe_text = safe_text.replace(word, "[REDACTED_EMAIL]");
}
}
}
// 3. Output the cleaned text back to standard output
io::stdout().write_all(safe_text.as_bytes()).unwrap();
}
- Code compilation
rustup target add wasm32-wasip1
rustc --target wasm32-wasip1 -O src/main.rs -o redactor.wasm
The UI Application and the Web Interface
- The public folder code;
// worker.js
import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.0-alpha.5';
env.allowLocalModels = false;
let summarizer = null;
/**
* Initializes the genAI model inside the WebAssembly runtime env.
* Uses lightweight Qwen 1.5 Chat model.
*/
async function initSummarizer() {
if (!summarizer) {
summarizer = await pipeline('text-generation', 'Xenova/Qwen1.5-0.5B-Chat', {
device: 'wasm',
model_file_name: 'decoder_model_merged'
});
}
}
self.addEventListener('message', async (event) => {
const { safeText } = event.data;
try {
await initSummarizer();
const prompt = `<|im_start|>system\nYou are a helpful assistant that writes brief summaries.<|im_end|>\n<|im_start|>user\nSummarize this text briefly: ${safeText}<|im_end|>\n<|im_start|>assistant\n`;
const output = await summarizer(prompt, {
max_new_tokens: 50,
temperature: 0.3,
do_sample: false
});
let cleanResult = output[0].generated_text.replace(prompt, '').trim();
self.postMessage({ summary: cleanResult });
} catch (err) {
self.postMessage({ summary: `Error in client execution: ${err.message}` });
}
});
- The HTML interface
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hybrid Wasm AI Application</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; background: #fafafa; }
textarea { width: 100%; height: 100px; margin-bottom: 10px; border: 1px solid #ccc; padding: 10px; border-radius: 4px; font-size: 14px;}
button { background: #0066cc; color: white; border: none; padding: 12px 20px; cursor: pointer; border-radius: 4px; font-weight: bold; width: 100%;}
button:hover { background: #0052a3; }
.box { background: white; border: 1px solid #ddd; padding: 15px; margin-top: 15px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
</style>
</head>
<body>
<h2>Hybrid Wasm AI Summarizer</h2>
<p><small>Type text containing sensitive emails to see the Server-Wasm process it, and Client-Wasm summarize it.</small></p>
<textarea id="rawInput" placeholder="Enter text here...">John Doe can be reached at john@company.com. He reported that the server infrastructure deployment failed because the configuration keys were corrupted during the midnight automation run. We need a recovery strategy formulated immediately.</textarea>
<button onclick="processText()">Run Hybrid Wasm Pipeline</button>
<div class="box">
<strong>1. Server Wasm Response (Redacted Safe Text):</strong>
<p id="serverOutput" style="color: #666; font-style: italic;">Waiting...</p>
</div>
<div class="box">
<strong>2. Client Wasm Output (Local AI Summary):</strong>
<p id="clientOutput" style="color: #0066cc; font-weight: bold;">Waiting...</p>
</div>
<script type="module">
const aiWorker = new Worker('worker.js', { type: 'module' });
window.processText = async function() {
const rawText = document.getElementById('rawInput').value;
document.getElementById('serverOutput').innerText = "Server-Wasm redacting PII...";
document.getElementById('clientOutput').innerText = "Waiting for clean text...";
// 1. Send text to Server-Side WebAssembly Pipeline via API
const response = await fetch('/api/redact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: rawText })
});
const data = await response.json();
const safeText = data.cleanedText;
document.getElementById('serverOutput').innerText = safeText;
// 2. Feed the output directly into Browser Client-Side Wasm
document.getElementById('clientOutput').innerText = "Client-Wasm initializing local model & generating summary...";
aiWorker.postMessage({ safeText });
};
aiWorker.onmessage = (event) => {
const { summary } = event.data;
document.getElementById('clientOutput').innerText = summary;
};
</script>
</body>
</html>
The ‘server’ Application to manage Api Requests
- The ‘
server.js’ code;
const express = require('express');
const path = require('path');
const fs = require('fs');
const { WASI } = require('wasi');
const app = express();
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.post('/api/redact', async (req, res) => {
try {
const { text } = req.body;
// WASI pipes
const wasi = new WASI({
version: 'preview1',
args: [],
env: {}
});
const wasmPath = path.join(__dirname, 'redactor.wasm');
const wasmBuffer = fs.readFileSync(wasmPath);
const { instance } = await WebAssembly.instantiate(wasmBuffer, {
wasi_snapshot_preview1: wasi.wasiImport
});
const cleanedText = text.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[REDACTED_EMAIL]");
res.json({ cleanedText });
} catch (error) {
console.error(error);
res.status(500).json({ error: "Failed executing server Wasm process" });
}
});
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
- Required installations for the
Node.jspart;
npm install
##
npm start
Testing the Application
-
The UI: Open a browser and navigate to:
http://localhost:3000 - Enter text containing an email address and click Run Hybrid Wasm Pipeline.
- Note on Initial Execution: On the very first run, the browser’s Web Worker will download the model weights (~350MB) from the CDN. Openning the browser Console (F12), we can track download progress streams. Once cached, all subsequent calculations execute instantly and completely offline.
- Sandboxed Server Execution: The data scrubber runs inside an isolated WASI context. It has zero access to host system environment variables, filesystems, or socket networks, mitigating arbitrary code injection attacks.
- Zero Server Infrastructure Fees: Heavy structural math, tensor manipulation, and token generations are completely offloaded to the client’s local CPU via browser-embedded Wasm.
Conclusion
Ultimately, building this hybrid application proves that WebAssembly is far more than just a tool for optimizing heavy mathematical computations. The true lesson of Wasm lies in its ability to completely decouple high-performance execution from native operating systems and traditional language boundaries. On the server, WASI demonstrates how we can trap untrusted processing logic inside a strict, deny-by-default sandbox, fundamentally redefining how we secure application runtimes. On the client, it proves we can comfortably shift massive, complex compute workloads — like running a local neural network — directly into a standard browser tab without forcing data over the network or paying massive infrastructure costs. WebAssembly offers inherently more portable applications, dramatically cheaper to scale, and secure by design.
>>> Thanks for reading <<<
Links
- WebAssembly Org: https://webassembly.org/
- WebAssembly: https://developer.mozilla.org/en-US/docs/WebAssembly
- What is Binary Format: https://webassembly.github.io/spec/core/binary/index.html
- Rust Installation: https://rust-lang.org/tools/install/
Some lecture…
- Server Side WebAssembly: https://www.manning.com/books/server-side-webassembly
- Refactoring to Rust: https://www.manning.com/books/refactoring-to-rust
- WebAssembly in Action: https://www.manning.com/books/webassembly-in-action
- Learn WebAssembly: https://www.packtpub.com/en-fr/product/learn-webassembly-9781788995467




Top comments (0)