Preface

Why not develop Cloudflare Workers services using Ruby?

This web book is a hands-on guide to Uzumibi, a framework for developing edge services with Ruby. You'll learn its core features step by step as you build real projects.

Uzumibi is powered by mruby/edge, an implementation of mruby. The resulting artifacts are tiny — for typical use cases, they can be under 500 KiB even after compression.

We hope this book opens up a new frontier for you at the intersection of Ruby and the edge.


Uzumibi Logo

Introduction

Before discussing Uzumibi, let's first organize the concepts around mruby/edge, the mruby implementation that serves as Uzumibi's foundation. After that, we'll provide an overview of Uzumibi itself.

What is mruby/edge

mruby/edge is a lightweight mruby implementation specialized for WebAssembly (Wasm) environments. It leverages the bytecode specification of mruby, a lightweight Ruby VM, and was developed with the goal of running efficiently in edge computing and serverless environments.

What is mruby

mruby is a lightweight implementation of the Ruby language designed to run in embedded environments and resource-constrained systems. It is primarily developed under the leadership of Yukihiro Matsumoto, the creator of Ruby. Compared to CRuby (the standard Ruby implementation), mruby has a smaller binary size and lower memory usage, making it suitable for embedding in various environments such as IoT devices and game engines.

https://mruby.org/

Recently, other implementations compatible with mruby's bytecode have been developed and are gaining attention, such as the even smaller mruby/c and PicoRuby, which offers a more practical embedded ecosystem.

Features of mruby/edge

mruby/edge is a re-implementation of the VM that executes mruby bytecode (.mrb files) written in Rust. It has the following features:

  • First-class WebAssembly support: Designed to generate portable Wasm code. Currently supports compilation to wasm32-unknown-unknown and wasm32-wasip1 targets, running on browsers, Wasmtime, Cloudflare Workers, Fastly Compute@Edge, and various other WASM runtimes.
  • Rust implementation: Since the VM is written in Rust, it offers high memory safety and smooth compilation to Wasm.
  • mruby 3.2.0 compatibility: Supports opcodes from mruby 3.2.0 through 3.4.0, enabling use of basic Ruby syntax and data types.
  • SharedMemory: Provides a mechanism for efficient data exchange with the host environment (JavaScript, etc.) by utilizing Wasm's linear memory.

From mruby Code to WASM

The flow of converting Ruby code to Wasm with mruby/edge is as follows:

Ruby source code (.rb)
    ↓ Compile with mruby compiler (mrbc, mruby-compiler2, etc.)
mruby bytecode (.mrb)
    ↓ Embed in Rust binary
    ↓ Compile to Wasm with cargo
Wasm module (.wasm)

This Wasm module can run in browsers. Additionally, by executing it on edge platforms such as Cloudflare Workers or Fastly, applications written in Ruby can be run at the edge.

It is also possible to embed mruby-compiler2 into Rust to directly execute Ruby scripts with mruby/edge. You can try it out on the Playground.

https://mrubyedge.github.io/playground/

Supported Ruby Features

The following Ruby language features are supported in mruby/edge:

  • Basic operations (arithmetic, comparison)
  • Conditional branching (if/elsif/else, case/when)
  • Method definition and recursive calls
  • Class definition and instance variables
  • attr_reader declarations
  • Global variables ($var)
  • Arrays (Array), Hashes (Hash), Strings (String)
  • Range objects (Range)
  • Blocks and iterators
  • Exception handling (raise/rescue)
  • String unpack/pack (binary data processing)

The specific supported methods can be checked in the COVERAGE.md file. It may also be useful to have AI reference this file.

Additionally, the following libraries are available as mrubyedge gems:

  • mruby-random
  • mruby-regexp
  • mruby-math
  • mruby-serde-json
  • mruby-time

Summary of mruby/edge

mruby/edge plays a crucial role as the foundation of the Uzumibi framework, enabling Ruby-written web application logic to run in edge environments.

What is Uzumibi

Uzumibi is a lightweight framework for building web applications in Ruby on edge computing platforms, built on top of mruby/edge.

The name "Uzumibi" is inspired by the popular edge framework Hono. Hono + Embedded (埋まっている / "buried") = Uzumibi (うずみび / "buried embers") 😅

Overview of Uzumibi

Uzumibi is a web application framework that allows you to define routing using a Sinatra-like DSL. You can write applications that handle HTTP requests with Ruby code like the following:

class App < Uzumibi::Router
  get "/" do |req, res|
    res.status_code = 200
    res.headers = {
      "content-type" => "text/plain",
      "x-powered-by" => "#{RUBY_ENGINE} #{RUBY_VERSION}"
    }
    res.body = "It works!\n"
    res
  end

  get "/greet/to/:name" do |req, res|
    res.status_code = 200
    res.headers = {
      "content-type" => "text/plain",
    }
    res.body = "Hello, #{req.params[:name]}!!\n"
    res
  end
end

$APP = App.new

How It Works

Uzumibi applications operate in a two-layer architecture:

  1. Wasm layer (Rust + mruby/edge): Ruby code is compiled into mruby bytecode and embedded in a Wasm module. Request processing is handled by Ruby code running on the mruby/edge VM.
  2. Platform layer (JavaScript / Rust): Glue code that bridges the edge platform's native APIs with the Wasm module. It handles binary serialization of HTTP requests/responses.

The request flow looks like this:

Client
    → Edge platform
    → Glue code (JS/Rust) serializes request to binary
    → Ruby code executes on mruby/edge VM inside WASM module
    → Response is deserialized from binary
    → Response returned to client

Routing

Uzumibi's router supports the following HTTP methods:

  • get
  • post
  • put
  • delete
  • head
  • options

URL paths can include dynamic parameters (:name) and wildcards (*). Parameters are accessible via req.params, which is a Hash.

# Dynamic parameter example
get "/users/:id" do |req, res|
  user_id = req.params[:id]
  # ...
end

In this book, we refer to the Ruby blocks defined with get, post, etc. as "route handlers".

Request Object

The req object (Uzumibi::Request) passed to the route handler block holds the following information:

  • req.method - HTTP method (GET, POST, etc.)
  • req.path - Request path
  • req.query - Query string
  • req.headers - Request headers (Hash)
  • req.body - Request body
  • req.params - A Hash that integrates URL parameters, query parameters, and form data

Response Object

The res object (Uzumibi::Response) is used to construct the response by setting the following properties:

  • res.status_code - HTTP status code (integer)
  • res.headers - Response headers (Hash)
  • res.body - Response body (string)

The route handler block must always return the res object.

External Service Integration

Uzumibi also supports access to external services provided by edge platforms. On Cloudflare Workers, the following are available:

  • Uzumibi::Fetch - Call external HTTP APIs
  • Uzumibi::KV - Key-Value store using Durable Objects
  • Uzumibi::Queue - Asynchronous messaging with Cloudflare Queues

These features are explained in detail in later chapters.

Note: As of March 14, 2026, external services are not available on platforms other than Cloudflare Workers. Support for other platforms will be added progressively. Pull requests are always welcome!

Project Setup and Hello World

In this chapter, we'll get Uzumibi up and running by installing the necessary tools, creating a hello world project, and verifying that everything works.

Installing uzumibi-cli

To start developing with Uzumibi, you first need to install uzumibi-cli and its prerequisite tools.

Prerequisites

The following tools must be installed beforehand.

Rust Toolchain

The Rust compiler is required to build Uzumibi projects. Install it using rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

After installation, add the WASM target:

rustup target add wasm32-unknown-unknown

Verify the version:

$ rustc --version
rustc 1.93.1 (01f6ddf75 2026-02-11)

Node.js and pnpm

For Cloudflare Workers, Node.js is required for development and deployment with Wrangler.

# Installing Node.js (example using a version manager)
# With fnm
fnm install --lts
# With nvm
nvm install --lts

# Installing pnpm
npm install -g pnpm

Verify the versions:

$ node --version
v25.6.1

$ pnpm --version
10.29.3

Wrangler CLI

A CLI tool used for developing and deploying Cloudflare Workers. It will be automatically installed locally via pnpm install after project creation, but installing it globally can be convenient:

pnpm install -g wrangler

clang / Build Tools

The C compiler clang is required for building the mruby compiler (mruby-compiler2).

For macOS:

xcode-select --install

For Linux:

# Ubuntu/Debian
sudo apt-get install clang build-essential

# Fedora
sudo dnf install clang

Installing uzumibi-cli

uzumibi-cli is installed using Rust's package manager cargo:

cargo install 'uzumibi-cli@^0.6'

Once installation is complete, verify the version:

$ uzumibi --version
uzumibi-cli 0.6.1

You can view the help to see available commands:

$ uzumibi --help

With this, your development environment is ready.

Creating a Project

Use the uzumibi new command to create a project for Cloudflare Workers.

Generating the Project

Create a project named hello-uzumibi with the following command:

uzumibi new --template cloudflare hello-uzumibi

Running this command will create a project directory and generate the necessary files:

Creating project 'hello-uzumibi'...
  generate  hello-uzumibi/.gitignore
  generate  hello-uzumibi/Cargo.toml
  generate  hello-uzumibi/package.json
  generate  hello-uzumibi/vitest.config.js
  generate  hello-uzumibi/wrangler.jsonc
  generate  hello-uzumibi/lib/app.rb
  generate  hello-uzumibi/public/assets/index.html
  generate  hello-uzumibi/src/index.js
  generate  hello-uzumibi/wasm-app/Cargo.toml
  generate  hello-uzumibi/wasm-app/build.rs
  generate  hello-uzumibi/wasm-app/src/lib.rs

✓ Successfully created project from template 'cloudflare'
  Run 'cd hello-uzumibi' to get started!

Project Directory Structure

The generated project has the following structure:

hello-uzumibi/
├── Cargo.toml          # Rust workspace configuration
├── package.json        # Node.js dependencies and scripts
├── wrangler.jsonc      # Wrangler (Cloudflare Workers CLI) configuration
├── lib/
│   └── app.rb          # Ruby application code (main)
├── public/
│   └── assets/...      # Static assets (HTML, CSS, images, etc.)
├── src/
│   └── index.js        # JavaScript glue code (entry point)
└── wasm-app/
    ├── Cargo.toml      # WASM crate configuration
    ├── build.rs        # Build script (compiles Ruby code)
    ├── src/
    │   └── lib.rs      # Rust code for the WASM module
    └── .cargo/
        └── config.toml # Cargo target settings (may not exist)

Role of Each File

lib/app.rb

This is the main file that developers edit. You write Uzumibi's routing and request handling logic in Ruby here.

src/index.js

The entry point for Cloudflare Workers. It receives HTTP requests, serializes them to binary format and passes them to the WASM module, then deserializes the response and returns it to the client. Normally, you don't need to edit this file.

wasm-app/

A Rust crate for compiling Ruby code into mruby bytecode and packaging it as a WASM module. Normally, you don't need to edit these files.

  • build.rs contains configuration to compile lib/app.rb into mruby bytecode (.mrb) at build time.
  • src/lib.rs handles mruby/edge VM initialization and export function definitions.

wrangler.jsonc

The Cloudflare Workers configuration file. It contains the application name, static asset settings, and more.

{
    "name": "hello-uzumibi",
    "main": "src/index.js",
    "compatibility_date": "2025-12-30",
    "assets": {
        "directory": "./public",
        "binding": "ASSETS"
    }
}

package.json

Defines npm/pnpm scripts and dependencies.

{
    "scripts": {
        "deploy": "wrangler deploy",
        "dev": "cargo build --package hello-uzumibi --target wasm32-unknown-unknown --release && cp -v -f target/wasm32-unknown-unknown/release/hello_uzumibi.wasm src/ && wrangler dev",
        "start": "wrangler dev",
        "test": "vitest"
    }
}

The dev script performs both the Rust build (WASM compilation) and Wrangler dev server startup in one step.

Installing Dependencies

Navigate to the project directory and install the Node.js dependencies:

cd hello-uzumibi
pnpm install

This installs development tools including Wrangler locally within the project.

Implementing a Hello World Application

Now that the project is created, let's implement an actual application. Uzumibi allows you to work with both Ruby code (lib/app.rb) and a static frontend (public/ directory).

Frontend Implementation

First, let's place a static HTML file in the public/ directory. Create public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Uzumibi</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 0 20px;
        }
        #result {
            margin-top: 20px;
            padding: 15px;
            background: #f0f0f0;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>Hello Uzumibi!</h1>
    <p>Building Cloudflare Workers with Ruby</p>

    <button id="greetBtn">Call API</button>
    <div id="result"></div>

    <script>
        document.getElementById('greetBtn').addEventListener('click', async () => {
            const res = await fetch('/api/hello');
            const text = await res.text();
            document.getElementById('result').textContent = text;
        });
    </script>
</body>
</html>

Files placed in the public/ directory are automatically served by Cloudflare Workers' static asset feature.

API Implementation

Next, edit lib/app.rb to implement the API endpoints. Replace the default generated code with the following:

class App < Uzumibi::Router
  # Root path delegates to static assets (public/index.html)
  get "/" do |req, res|
    fetch_assets
  end

  # Simple API endpoint
  get "/api/hello" do |req, res|
    res.status_code = 200
    res.headers = {
      "content-type" => "text/plain",
      "x-powered-by" => "#{RUBY_ENGINE} #{RUBY_VERSION}"
    }
    res.body = "Hello from Uzumibi! Running on #{RUBY_ENGINE} #{RUBY_VERSION}\n"
    res
  end

  # Greeting API with dynamic parameters
  get "/api/greet/:name" do |req, res|
    name = req.params[:name]
    res.status_code = 200
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = JSON.generate({ message: "Hello, #{name}!" })
    res
  end

  # POST request handling
  post "/api/echo" do |req, res|
    res.status_code = 200
    res.headers = {
      "content-type" => "text/plain",
    }
    res.body = "Received: #{req.body.inspect}\n"
    res
  end
end

$APP = App.new

Code Walkthrough

Routing

Define routes using class methods like get and post within a class that inherits from Uzumibi::Router:

class App < Uzumibi::Router
  get "/path" do |req, res|
    # Handler logic
  end
end

Each route handler block receives two arguments: req (request) and res (response).

Serving Static Assets

get "/" do |req, res|
  fetch_assets
end

Calling the fetch_assets method delegates the request to the static assets in the public/ directory. This causes public/index.html to be returned for the / path.

Note that if you define a handler for "/file" and a request comes in for /file, public/file.html will be returned if it exists. Be aware that the file extension is automatically appended.

Building a Response

Set status_code, headers, and body on the response object, then return res:

get "/api/hello" do |req, res|
  res.status_code = 200
  res.headers = {
    "content-type" => "text/plain",
  }
  res.body = "Hello!\n"
  res  # Always return res
end

Dynamic Parameters

Including :parameter_name in the URL path captures that path segment as a dynamic parameter. Access it through the req.params Hash:

get "/api/greet/:name" do |req, res|
  name = req.params[:name]
  # ...
end

Request Body

The body of POST requests and similar can be accessed synchronously via req.body. If the request's content-type is form data (application/x-www-form-urlencoded) or JSON data (application/json), it is automatically parsed, and you can access the data through req.params as well.

The $APP Global Variable

$APP = App.new

In Uzumibi 0.6.x, the convention is to assign the application instance to the global variable $APP at the end. This variable is referenced from the Rust code on the Wasm side, so it must always be set.

Starting the Dev Server

Once the application code is written, let's start the development server to verify it works.

Launching the Dev Server

Start the development server with the following command:

pnpm run dev

This command internally executes the following steps in sequence:

  1. Rust build: Runs the cargo command to compile lib/app.rb into mruby bytecode and then generate a Wasm module (.wasm).
  2. Wasm file copy: Copies the built .wasm file to the src/ directory.
  3. Wrangler dev startup: Runs wrangler dev to start the local development server.

The first build may take several minutes as it needs to download and compile Rust crates. Subsequent builds will be faster due to incremental compilation.

When the build is complete, you'll see a message like this:

 ⛅️ wrangler 4.73.0
───────────────────

Your Worker has access to the following bindings:
Binding            Resource      Mode
env.ASSETS         Assets        local
...

⎔ Starting local server...
[wrangler:info] Ready on http://localhost:8787

Verifying Operation

Once the development server is running, verify the operation using a browser or curl.

Browser Verification

Access http://localhost:8787 in your browser to see the contents of public/index.html. Clicking the "Call API" button will display the response from the /api/hello endpoint.

You can adjust the JavaScript in the HTML to call other endpoints and check their output as well.

curl Verification

Let's call the API endpoints using curl:

# Root path (static assets)
$ curl http://localhost:8787/
<!DOCTYPE html>
<html lang="en">
...

# API endpoint
$ curl http://localhost:8787/api/hello
Hello from Uzumibi! Running on mruby/edge 3.2.0

# Dynamic parameters
$ curl http://localhost:8787/api/greet/World
{"message": "Hello, World!"}

# POST request
$ curl -X POST -d "test data" http://localhost:8787/api/echo
Received: test data

Reflecting Code Changes

If you modify lib/app.rb, the current version of Uzumibi does not support automatic reloading. Since a Wasm rebuild is required, stop the development server (Ctrl+C) and run pnpm run dev again:

# Stop with Ctrl+C
# Edit code
# Restart
pnpm run dev

Troubleshooting

Build Error: clang not found

clang is required for building the mruby compiler. On macOS, install it with xcode-select --install; on Linux, use apt install clang.

Build Error: wasm32-unknown-unknown target not found

The Wasm target may not have been added.

rustup target add wasm32-unknown-unknown

Wrangler Authentication Error

If this is your first time using Wrangler, you may need to log in:

npx wrangler login

Port Conflict

If the default port 8787 is in use by another process, Wrangler will automatically use a different port. Check the URL displayed in the console output.

Deployment

Once you've verified your application works on the development server, let's deploy it to Cloudflare Workers.

Preparing a Cloudflare Account

You need a Cloudflare account to deploy. If you don't have one yet, sign up at the Cloudflare dashboard. You can use Cloudflare Workers on the free plan.

Logging in to Wrangler

Log in to your Cloudflare account from the Wrangler CLI:

npx wrangler login

A browser window will open asking you to authenticate with Cloudflare. Once authentication is complete, a login success message will appear in the terminal.

Building WASM

Before deploying, you need to build the WASM module. If you've already run pnpm run dev at least once, the built .wasm file should already be in the src/ directory. If you haven't built yet, you can run just the build with the following commands:

cargo build --package hello-uzumibi --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/hello_uzumibi.wasm src/

Running the Deployment

Deploy to Cloudflare Workers with the following command:

pnpm run deploy

This command internally runs wrangler deploy.

When the deployment starts, you'll see a message like this:

 ⛅️ wrangler 4.73.0
───────────────────
🌀 Building list of assets...
✨ Read 3 files from the assets directory /Users/udzura/zenn-wip/hello-uzumibi/public
🌀 Starting asset upload...

Pay attention to the size display in the logs. Artifacts generated by Uzumibi are approximately 650 KiB before compression and around 200 KiB with gzip compression:

Total Upload: 655.99 KiB / gzip: 204.28 KiB

On Cloudflare Workers' free plan, you can upload assets up to 3 MB, and the recommended configuration is to keep compressed size under 1 MB.

While larger applications may be difficult to serve on Cloudflare Workers, Uzumibi and mruby/edge applications are designed to provide minimal functionality to keep the size as small as possible. This makes them very advantageous for edge application development.

The URL displayed at the end will allow you to access your deployed application:

Uploaded hello-uzumibi (14.81 sec)
Deployed hello-uzumibi triggers (7.49 sec)
  https://hello-uzumibi.XXXXXX.workers.dev
Current Version ID: b7bcc75c-e557-4c39-b643-xxxxxxxx

Verifying the Deployment

Access https://hello-uzumibi.<your-subdomain>.workers.dev/ in your browser to see the HTML page you created earlier.

wrangler.jsonc Configuration

The deployed Worker's name and settings are managed in wrangler.jsonc:

{
    "name": "hello-uzumibi",        // Worker name (becomes the URL subdomain)
    "main": "src/index.js",         // Entry point
    "compatibility_date": "2025-12-30",
    "assets": {
        "directory": "./public",    // Static assets directory
        "binding": "ASSETS"
    }
}

If you want to change the Worker name, edit the "name" field and redeploy.

Custom Domain Configuration

By default, the application is deployed to a *.workers.dev domain, but you can also configure a custom domain. Set it up from the Cloudflare dashboard under "Workers & Pages", or add routes to wrangler.jsonc.

See the Cloudflare Workers documentation for details.

Chapter Summary

In this chapter, we walked through the basic development workflow for a Cloudflare Workers application using Uzumibi.

What We Learned

  1. Installing uzumibi-cli: We prepared prerequisite tools including the Rust toolchain, Node.js/pnpm, and Wrangler, then installed the CLI with cargo install uzumibi-cli.

  2. Creating a project: We generated a project with the uzumibi new --template cloudflare command and reviewed the directory structure and the role of each file.

  3. Implementing the application:

    • Created a static frontend page in public/index.html.
    • Implemented API endpoints in Ruby in lib/app.rb.
    • Learned the basic DSL of Uzumibi::Router (get, post, dynamic parameters, etc.).
  4. Development server: Started the local development server with pnpm run dev and verified operation using a browser and curl.

  5. Deployment: Deployed to Cloudflare Workers with pnpm run deploy and ran the application at a public URL.


In the following chapters, we'll cover the external service integration features (Fetch, Durable Object, Queue) available in Uzumibi on Cloudflare Workers.

Uzumibi and External Services

This chapter provides an overview of the external services supported by Uzumibi on Cloudflare Workers.

External Services Available on Uzumibi with Cloudflare Workers

On Cloudflare Workers, you can access external Web APIs through JavaScript's fetch() function, as well as various services provided by Cloudflare.

Uzumibi provides APIs for using some of these services from Ruby.

To use external service integration, specify the --features enable-external option when creating a project:

uzumibi new --template cloudflare --features enable-external my-app

Currently, Uzumibi on Cloudflare Workers supports the following three external services:

Fetch

Uzumibi::Fetch is an API for making external HTTP requests from within a Worker. Use it when calling external REST APIs or web services.

# Basic usage
response = Uzumibi::Fetch.fetch("https://api.example.com/data")
# response.status_code => 200
# response.body => Response body
# response.headers => Response headers (Hash)

The Uzumibi::Fetch.fetch method takes the following four arguments:

ArgumentTypeDescription
urlStringRequest URL (required)
methodStringHTTP method (defaults to "GET")
bodyStringRequest body (defaults to empty string)
headersHash[String, String]Request headers (defaults to none)

The return value is a Uzumibi::Response object with status_code, headers, and body.

Internally, it executes asynchronous HTTP requests through JavaScript's fetch() API.

Note: Functions like fetch() are asynchronous at the JavaScript level. To call asynchronous operations through Wasm, Uzumibi uses the asyncify feature from binaryen and the asyncify-wasm JavaScript library. Due to these libraries, the Wasm binary is slightly larger when external service integration is enabled.

Durable Object

Uzumibi::KV is a Key-Value store that uses Cloudflare Durable Objects. It allows you to persistently store data across requests.

# Save a value
Uzumibi::KV.set("key", "value")

# Get a value
value = Uzumibi::KV.get("key")
# => "value"

# Non-existent key
value = Uzumibi::KV.get("unknown")
# => nil

Durable Objects are Cloudflare Workers' stateful storage feature. In Uzumibi, a Durable Object class named UzumibiKVObject is automatically configured, and data is persisted using SQLite-based storage.

MethodArgumentsReturn ValueDescription
Uzumibi::KV.get(key)key: StringString or nilGet the value for a key
Uzumibi::KV.set(key, value)key: String, value: StringtrueSave a value for a key

Queue

Uzumibi::Queue is an API for sending messages to Cloudflare Queues. It can be used for asynchronous background processing. When using Queues, you need to create a Queue in advance through the Cloudflare dashboard or similar.

# Send a message
Uzumibi::Queue.send("UZUMIBI_QUEUE", "Hello from queue!")
MethodArgumentsReturn ValueDescription
Uzumibi::Queue.send(queue_name, message)queue_name: String, message: StringtrueSend a message to the specified queue

queue_name is the name of the queue binding defined in wrangler.jsonc. The implementation of the Queue receiver (Consumer) is explained in detail in Chapter 6.

Internal Behavior of External Service Features

Projects with external service integration enabled differ from standard projects in the following ways:

  1. Wasm module: The enable-external feature is added to wasm-app/Cargo.toml, incorporating Fetch/KV/Queue wrapper functions on the Rust side.
  2. JavaScript glue code: src/index.js is replaced with a version that uses the asyncify-wasm library, enabling asynchronous operations (await) inside Wasm.
  3. wrangler.jsonc: Durable Object bindings and migration settings are added. Queue enablement needs to be configured manually.

In the following chapters, we'll introduce examples that actually use the external services.

Calling External Public APIs with Fetch

In this chapter, we'll create an application that calls external Web APIs using Uzumibi::Fetch.

Creating the Project

Create a new project with external service integration enabled:

uzumibi new \
  --template cloudflare \
  --features enable-external \
  fetch-example
cd fetch-example
pnpm install

Selecting an API to Use

In this example, we'll use the freely available JSONPlaceholder API. This is a test API that returns dummy TODO data, user data, and more.

We'll primarily use the following endpoints:

  • GET https://jsonplaceholder.typicode.com/todos/1 - Get a TODO by ID
  • GET https://jsonplaceholder.typicode.com/todos?_limit=5 - Get a list of TODOs (with a limit)

Calling the API

Edit lib/app.rb as follows:

class App < Uzumibi::Router
  get "/" do |req, res|
    fetch_assets
  end

  # Get a single TODO from the external API
  get "/api/todo/:id" do |req, res|
    id = req.params[:id]
    api_url = "https://jsonplaceholder.typicode.com/todos/#{id}"

    debug_console("Fetching: #{api_url}")
    api_response = Uzumibi::Fetch.fetch(api_url)

    res.status_code = api_response.status_code
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = api_response.body
    res
  end

  # Get a list of TODOs from the external API
  get "/api/todos" do |req, res|
    api_url = "https://jsonplaceholder.typicode.com/todos?_limit=5"

    debug_console("Fetching: #{api_url}")
    api_response = Uzumibi::Fetch.fetch(api_url)

    res.status_code = api_response.status_code
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = api_response.body
    res
  end

  # Example of forwarding a POST request
  post "/api/todo" do |req, res|
    api_url = "https://jsonplaceholder.typicode.com/todos"

    debug_console("Posting to: #{api_url}")
    api_response = Uzumibi::Fetch.fetch(
      api_url, "POST",
      JSON.generate(req.body),
      {"content-type" => "application/json"}
    )

    res.status_code = api_response.status_code
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = api_response.body
    res
  end
end

$APP = App.new

Code Walkthrough

Forwarding GET Requests

api_response = Uzumibi::Fetch.fetch(api_url)

Simply passing a URL to Uzumibi::Fetch.fetch executes a GET request. The return value api_response is a Uzumibi::Response object that holds the response from the external API.

Forwarding POST Requests

api_response = Uzumibi::Fetch.fetch(api_url, "POST", req.body)

By specifying an HTTP method as the second argument and a request body as the third argument, you can send POST and other types of requests.

Specify header information such as authentication as a Hash in the fourth argument.

Debug Output

debug_console("Fetching: #{api_url}")

The debug_console method outputs debug messages to the Wrangler console (terminal). It is not displayed in the browser.

Verifying Operation

Start the development server:

pnpm run dev

Verify operation with curl:

# Get a single TODO
$ curl http://localhost:8787/api/todo/1
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

# Get a list of TODOs
$ curl http://localhost:8787/api/todos
[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },...
]

# POST request
$ curl -X POST -H "Content-Type: application/json" \
    -d '{"title":"New Todo","completed":false,"userId":1}' \
    http://localhost:8787/api/todo
{
  "completed": false,
  "title": "New Todo",
  "userId": 1,
  "id": 201
}

Debug messages like the following will appear in the Wrangler console:

[debug]: Fetching: https://jsonplaceholder.typicode.com/todos/1

We've implemented a sample application that calls external APIs from Uzumibi.

Chapter Summary

With Uzumibi::Fetch, you can easily integrate with external microservices and third-party APIs.

Saving Memos with Durable Object

In this chapter, we'll create a memo application that stores data persistently using Uzumibi::KV (based on Cloudflare Durable Objects).

Architecture Overview

Cloudflare Durable Objects is a service for managing stateful objects on Cloudflare Workers. While regular Workers are stateless (they don't retain state between requests), Durable Objects enable persistent data read/write operations.

In Uzumibi version 0.6.x, the Uzumibi::KV API wraps Durable Objects as a simple Key-Value store.

Client → Cloudflare Worker (Uzumibi)
            ↓ Uzumibi::KV.get/set
         UzumibiKVObject (Durable Object)
            ↓
         SQLite storage (persisted)

UzumibiKVObject is a Durable Object class defined in the JavaScript glue code. Internally, it uses the Durable Object's ctx.storage API (SQLite-based) to store data.

Creating the Project

Create a project with external service integration enabled:

uzumibi new \
  --template cloudflare \
  --features enable-external memo-app
cd memo-app
pnpm install

Check the generated wrangler.jsonc to see the automatically configured Durable Object bindings:

{
    "durable_objects": {
        "bindings": [
            {
                "name": "UZUMIBI_KV_DATA",
                "class_name": "UzumibiKVObject"
            }
        ]
    },
    "migrations": [
        {
            "tag": "v1",
            "new_sqlite_classes": [
                "UzumibiKVObject"
            ]
        }
    ]
}

Implementation

Edit lib/app.rb to create a simple memo save/retrieve API:

class App < Uzumibi::Router
  get "/" do |req, res|
    fetch_assets
  end

  # Get a memo
  get "/api/memo/:key" do |req, res|
    key = req.params[:key]
    value = Uzumibi::KV.get(key)

    if value
      res.status_code = 200
      res.headers = {
        "content-type" => "application/json",
      }
      res.body = JSON.generate({
        key: key,
        value: value
      })
    else
      res.status_code = 404
      res.headers = {
        "content-type" => "application/json",
      }
      res.body = JSON.generate({
        error: "not found",
        key: key
      })
    end
    res
  end

  # Save a memo
  post "/api/memo/:key" do |req, res|
    key = req.params[:key]
    value = req.body

    debug_console("Saving memo: #{key} = #{value}")
    Uzumibi::KV.set(key, value)

    res.status_code = 201
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = JSON.generate({
      key: key,
      value: value,
      status: "saved"
    })
    res
  end
end

$APP = App.new

Code Walkthrough

Getting a Value

value = Uzumibi::KV.get(key)

Uzumibi::KV.get returns the value for the specified key as a string. If the key doesn't exist, it returns nil.

Saving a Value

Uzumibi::KV.set(key, value)

Uzumibi::KV.set saves a key-value pair. Calling set again with the same key overwrites the value. Both keys and values are string types.

Verifying Operation

Start the development server:

pnpm run dev

Test saving and retrieving memos with curl:

# Save a memo
$ curl -X POST -d "Shopping list: milk, bread, eggs" \
  http://localhost:8787/api/memo/shopping
{"key": "shopping", "value": "Shopping list: milk, bread, eggs", "status": "saved"}

# Get a memo
$ curl http://localhost:8787/api/memo/shopping
{"key": "shopping", "value": "Shopping list: milk, bread, eggs"}

# Non-existent key
$ curl http://localhost:8787/api/memo/unknown
{"error": "not found", "key": "unknown"}

# Update a memo
$ curl -X POST -d "Shopping list: milk, bread, eggs, butter" \
  http://localhost:8787/api/memo/shopping
{"key": "shopping", "value": "Shopping list: milk, bread, eggs, butter", "status": "saved"}

Data stored in the Durable Object persists across Worker restarts and deployments.

Notes

  • Both keys and values in Uzumibi::KV are string types. If you want to store numbers or objects, convert them to JSON strings or similar before saving.
  • During local development (wrangler dev), Durable Object data is saved in local storage within the project's .wrangler/ directory.
  • When deployed to production, data is stored on Cloudflare's global network.

Communicating with Cloudflare Queue

Cloudflare Queues is a message queue service. It provides the basic message queuing pattern where a producer (sender) sends messages to a queue and a consumer (receiver) processes them asynchronously.

Queue Use Cases

Publisher

A Publisher (producer) is responsible for sending messages to a queue. In Uzumibi, you use the Uzumibi::Queue.send method to send messages.

Typical use cases include:

  • Asynchronous background processing: Return a response to the user immediately while delegating time-consuming tasks (email sending, image conversion, data aggregation, etc.) to the queue.
  • Event notification: Notify another Worker of events that occurred in one Worker (user registration, order completion, etc.) to trigger subsequent processing.
  • Rate limit mitigation: Put requests to external APIs into a queue first, then process them sequentially on the consumer side in a controlled manner.
# Example: Send a welcome email asynchronously after user registration
post "/api/users" do |req, res|
  # User registration processing (completes immediately)
  # ...

  # Delegate email sending to the queue (asynchronous)
  Uzumibi::Queue.send("UZUMIBI_QUEUE", "welcome_email:#{user_email}")

  res.status_code = 201
  res.body = JSON.generate({status: "registered"})
  res
end

Consumer

A Consumer is responsible for receiving and processing messages from the queue.

Note: In a standard Cloudflare project, the web application that sends to a Cloudflare Queue and the process that receives messages from the queue can be defined in the same Worker file. However, in Uzumibi 0.6.x, you need to generate a separate consumer-only application, so please keep this in mind.

In the Ruby code of the consumer application, you prepare a class that inherits from Uzumibi::Consumer and override the on_receive method to implement the processing logic.

Consumers have the following characteristics:

  • Batch processing: Messages are received in batches (up to 10 messages, with a maximum wait time of 5 seconds).
  • Retry functionality: Failed messages can be retried after a specified delay.
  • Acknowledgment: Successfully processed messages are acknowledged with ack! and removed from the queue.
class Consumer < Uzumibi::Consumer
  def on_receive(message)
    debug_console("Processing: #{message.body}")

    # Acknowledge after successful processing
    message.ack!
  end
end

The Message object has the following attributes:

AttributeTypeDescription
message.idStringUnique ID of the message
message.timestampStringTimestamp when the message was sent (ISO 8601 format)
message.bodyStringMessage body
message.attemptsIntegerNumber of delivery attempts so far

The Message object also has the following methods:

MethodDescription
message.ack!Notify that message processing is complete and remove it from the queue
message.retry(delay_seconds: N)Redeliver the message after N seconds

Queue Architecture and Setup

When building a Uzumibi application using Cloudflare Queues, you configure two Workers: a sender (Publisher) and a receiver (Consumer). This section explains the overall architecture and setup process.

Architecture

Client
    ↓ HTTP request
[Publisher Worker] (generated with enable-external feature)
    ↓ Uzumibi::Queue.send
[Cloudflare Queue]
    ↓ Message delivery
[Consumer Worker] (generated with queue feature)
    ↓ on_receive(message)
    Message processing → ack! or retry

The sender and receiver operate as separate Workers, both bound to the same queue. The sender sends messages through the queues.producers binding, and the receiver receives messages through the queues.consumers binding.

Creating a Cloudflare Queue

First, you need to create a queue in Cloudflare Queues. Create one from the Cloudflare dashboard or using the Wrangler CLI:

$ npx wrangler queues create my-app-queue
...
 ⛅️ wrangler 4.73.0
───────────────────
🌀 Creating queue 'my-app-queue'
✅ Created queue 'my-app-queue'

Verify that the queue was created:

$ npx wrangler queues list
┌──────────────────────────────────┬───────────────┬─────────────────────────────┬─────────────────────────────┬───────────┬───────────┐
│ id                               │ name          │ created_on                  │ modified_on                 │ producers │ consumers │
├──────────────────────────────────┼───────────────┼─────────────────────────────┼─────────────────────────────┼───────────┼───────────┤
│ 7941fbb9762d4a02b1a1c644XXXXXXXX │ my-app-queue  │ 2026-03-14T12:48:00.918834Z │ 2026-03-14T12:48:00.918834Z │ 0         │ 0         │
└──────────────────────────────────┴───────────────┴─────────────────────────────┴─────────────────────────────┴───────────┴───────────┘

Project Structure

Two projects are needed for Queue communication:

  1. Sender project: Created with the enable-external feature, sends messages using Uzumibi::Queue.send.
  2. Receiver project: Created with the queue feature, receives and processes messages using Uzumibi::Consumer.
# Sender
uzumibi new --template cloudflare --features enable-external queue-publisher
# Receiver
uzumibi new --template cloudflare --features queue queue-consumer

Note: When the queue feature is enabled, the enable-external feature is also automatically enabled in the generated project.

wrangler.jsonc Configuration

Sender Configuration

In the sender's wrangler.jsonc, configure the queue binding in queues.producers. The default template has this commented out, so uncomment it to enable:

{
    "queues": {
        "producers": [
            {
                "binding": "UZUMIBI_QUEUE",
                "queue": "my-app-queue"
            }
        ]
    }
}

binding is the name used to reference the queue in code, corresponding to the first argument of Uzumibi::Queue.send. queue specifies the actual queue name created in Cloudflare Queues.

Receiver Configuration

In the receiver's wrangler.jsonc, the queues.consumers queue binding is automatically configured from the project name. For this example, manually change it to the correct name:

{
    "queues": {
        "producers": [
            {
                "binding": "UZUMIBI_QUEUE",
                "queue": "my-app-queue"
            }
        ],
        "consumers": [
            {
                "queue": "my-app-queue",
                "max_batch_size": 10,
                "max_batch_timeout": 5
            }
        ]
    }
}

Configure the following values as needed:

SettingDescription
queueName of the queue to receive from
max_batch_sizeMaximum number of messages to receive at once (default: 10)
max_batch_timeoutMaximum wait time in seconds for a batch to fill (default: 5)

Publisher Implementation

The publisher Worker (queue-publisher) receives HTTP requests and sends messages to a Cloudflare Queue.

Application Implementation

Edit lib/app.rb as follows:

class App < Uzumibi::Router
  # Message sending API
  post "/api/send" do |req, res|
    message = req.body

    if message == "" || message == nil
      res.status_code = 400
      res.headers = {
        "content-type" => "application/json",
      }
      res.body = JSON.generate({ error: "message body is required" })
    else
      debug_console("Sending message to queue: #{message}")
      Uzumibi::Queue.send("UZUMIBI_QUEUE", message)

      res.status_code = 202
      res.headers = {
        "content-type" => "application/json",
      }
      res.body = JSON.generate({ status: "accepted", message: message })
    end
    res
  end

  # Example of enqueuing a task
  post "/api/tasks/:task_type" do |req, res|
    task_type = req.params[:task_type]
    payload = req.body

    # Send task type and payload in JSON format
    queue_message = JSON.generate({ type: task_type, payload: payload })
    debug_console("Enqueuing task: #{queue_message}")
    Uzumibi::Queue.send("UZUMIBI_QUEUE", queue_message)

    res.status_code = 202
    res.headers = {
      "content-type" => "application/json",
    }
    res.body = JSON.generate({ status: "queued", task_type: task_type })
    res
  end
end

$APP = App.new

Code Walkthrough

Sending a Message

Uzumibi::Queue.send("UZUMIBI_QUEUE", message)

The first argument of Uzumibi::Queue.send specifies the binding name configured in queues.producers in wrangler.jsonc. The second argument is the message body (string).

HTTP Status Code 202

Since enqueuing a message means starting asynchronous processing, we return HTTP status code 202 Accepted. The actual message processing is performed by the consumer Worker.

Verifying Operation

Start the development server and test message sending:

pnpm run dev
# Send a message
$ curl -X POST -d "Hello, Queue\!" http://localhost:8787/api/send
{"status": "accepted", "message": "Hello, Queue!"}

# Enqueue a task
$ curl -X POST -d "user@example.com" \
    http://localhost:8787/api/tasks/send_email
{"status": "queued", "task_type": "send_email"}

During local development, queue messages are actually sent to a dummy server on localhost. Debug messages will be displayed in the Wrangler console:

[debug]: Sending message to queue: Hello, Queue!
[debug]: Enqueuing task: {"type": "send_email", "payload": "user@example.com"}

Deploy for Integration Testing

After basic verification, deploy to the remote Cloudflare Worker with the following command:

pnpm run deploy

After deployment, perform a quick operational check:

curl -X POST -d "Hello, Production Queue\!" \
    http://queue-publisher.<ID>.workers.dev/api/send
{"message":"Hello, Production Queue!","status":"accepted"}

Consumer Implementation

The consumer Worker receives messages from a Cloudflare Queue and processes them. In Uzumibi, you inherit from the Uzumibi::Consumer class and implement processing in the on_receive method.

Project Structure

When the queue feature is enabled, the following file is generated instead of the standard lib/app.rb:

  • lib/consumer.rb - Consumer Ruby code

Additionally, consumer-specific Wasm export functions (uzumibi_initialize_message, uzumibi_start_message) are added to wasm-app/src/lib.rs.

queue-consumer/
├── lib/
│   └── consumer.rb     # Queue consumer processing
├── src/
│   └── index.js        # JS glue code (handles both HTTP and Queue)
├── wasm-app/
│   ├── build.rs        # Compiles both app.rb and consumer.rb
│   └── src/
│       └── lib.rs      # Wasm (exports both HTTP and Queue processing)
├── wrangler.jsonc
└── package.json

The consumer Worker is dedicated to processing Queue messages.

Verifying wrangler.jsonc

The consumer's wrangler.jsonc contains queues.consumers configuration. Double-check that the queue name matches the sender:

{
    "queues": {
        "producers": [
            {
                "binding": "UZUMIBI_QUEUE",
                "queue": "my-app-queue"
            }
        ],
        "consumers": [
            {
                "queue": "my-app-queue",
                "max_batch_size": 10,
                "max_batch_timeout": 5
            }
        ]
    }
}

Implementing the Consumer

Edit lib/consumer.rb to implement the message processing logic:

class Consumer < Uzumibi::Consumer
  # @rbs message: Uzumibi::Message
  def on_receive(message)
    debug_console("[Consumer] Received message: id=#{message.id}, body=#{message.body}, attempts=#{message.attempts}")

    # Process the message
    body = message.body
    debug_console("[Consumer] Processing: #{body}")

    # Acknowledge after successful processing
    if message.attempts > 3
      # Give up and ack after more than 3 retries
      debug_console("[Consumer] Giving up after #{message.attempts} attempts, acknowledging message #{message.id}")
      message.ack!
    else
      # Normal processing
      begin
        process_message(body)
        debug_console("[Consumer] Successfully processed message #{message.id}")
        message.ack!
      rescue => e
        debug_console("[Consumer] Error processing message: retrying in 5 seconds")
        message.retry(delay_seconds: 5)
      end
    end
  end

  def process_message(body)
    # Actual message processing logic
    debug_console("[Consumer] Message content: #{body}")
    # Write specific processing here
    # e.g., calling external APIs, saving data, etc.
  end
end

$CONSUMER = Consumer.new

Code Walkthrough

Inheriting Uzumibi::Consumer

class Consumer < Uzumibi::Consumer
  def on_receive(message)
    # ...
  end
end

Inherit from Uzumibi::Consumer and override the on_receive method. This method is called each time a message is received from the queue.

Message Attributes

The message object (Uzumibi::Message) passed to on_receive provides the following information:

message.id        # Message ID (string)
message.body      # Message body (string)
message.attempts  # Delivery attempt count (integer)
message.timestamp # Send timestamp (ISO 8601 format string)

Acknowledgment (ack!)

message.ack!

Call ack! on a message once processing is complete. Messages that have been acknowledged are removed from the queue.

Retry

message.retry(delay_seconds: 5)

If processing fails, call retry to redeliver the message. Specify the delay time until redelivery in seconds with delay_seconds. The next time on_receive is called, message.attempts will be incremented.

Global Variable $CONSUMER

$CONSUMER = Consumer.new

Assign the consumer instance to the global variable $CONSUMER. This is referenced from the Rust code on the Wasm side, so it must always be set.

Deploying for Verification

Note: With the current Wrangler, Queue communication across multiple projects cannot be verified in the localhost development environment.

Deploy queue-consumer for verification.

First, perform the build:

pnpm install
pnpm run dev
# The HTTP part is empty, so it starts but behavior can't be verified.
# This is just to build the Wasm, so stop it afterwards.

Verifying Operation

Testing

To check the received logs, display logs in the terminal with the following command:

Terminal (queue-consumer):

$ cd queue-consumer
$ npx wrangler tail

 ⛅️ wrangler 4.73.0
───────────────────
Successfully created tail, expires at 2026-03-14T19:19:39Z

In a separate terminal, issue the following curl command to send a message:

curl -X POST -d "Test message from publisher" \
    http://queue-publisher.<ID>.workers.dev/api/send
{"message":"Test message from publisher","status":"accepted"}

Message processing logs will appear in the queue-consumer console:

Queue my-app-queue (1 message) - Ok @ 2026/3/14 22:22:05
  (log) [debug]: [Consumer] Received message: id=25f4ef4ca8b2fdb7055cc5b9XXXXXXXX, body=Test message from publisher, attempts=1
  (log) [debug]: [Consumer] Processing: Test message from publisher
  (log) [debug]: [Consumer] Message content: Test message from publisher
  (log) [debug]: [Consumer] Successfully processed message 25f4ef4ca8b2fdb7055cc5b9XXXXXXXX

Processing status can also be checked from the Cloudflare dashboard under "Workers & Pages" > target Worker > "Logs".

Chapter Summary

Queues enable easy asynchronous processing with Cloudflare Workers + Uzumibi.

When using Queues, the architecture is somewhat special. Please note that with Uzumibi, two Workers are required.

Appendix: Uzumibi Reference Information

This chapter covers supplementary information that was not included in the main chapters.

Supported Platforms

Uzumibi supports multiple edge computing platforms. When creating a project with the uzumibi new command, you select the target platform using the --template option.

Platform List

PlatformTemplate NameStatusWASM Target
Cloudflare WorkerscloudflareBetawasm32-unknown-unknown
Fastly Compute@EdgefastlyExperimentalwasm32-wasip1
Spin (Fermyon Cloud)spinExperimentalwasm32-wasip1
Google Cloud RuncloudrunExperimentalNative (container)
Service WorkerserviceworkerExperimentalwasm32-unknown-unknown
Web WorkerwebworkerExperimentalwasm32-unknown-unknown

Cloudflare Workers

Cloudflare Workers is a platform for running serverless code on Cloudflare's global network. Uzumibi has the most advanced development for this platform, supporting the following features:

  • Basic HTTP request/response processing
  • Static asset serving (public/ directory)
  • External HTTP requests (Uzumibi::Fetch)
  • Data persistence with Durable Objects (Uzumibi::KV)
  • Asynchronous messaging with Cloudflare Queues (Uzumibi::Queue)

Development uses Node.js (pnpm), the Wrangler CLI, and the Rust toolchain.

Fastly Compute@Edge

Fastly Compute@Edge is a platform for running WASM applications on Fastly's CDN edge nodes. It uses glue code written in Rust for request processing.

Development uses the Fastly CLI and the Rust toolchain.

Spin (Fermyon Cloud)

Spin is a WebAssembly microservice framework developed by Fermyon. It can be deployed to Fermyon Cloud and run as a serverless application.

Development uses the Spin CLI and the Rust toolchain.

Google Cloud Run (Experimental)

Google Cloud Run is a container-based serverless platform. In Uzumibi, the application is packaged as an HTTP server using Tokio + Hyper in a container, ready to run on Cloud Run.

Unlike other platforms, it is built as a native binary rather than Wasm. A Dockerfile is automatically included, so you can build a container from it.

Since the project is created in a form that can also run on localhost, development is possible as long as Docker is installed. Having the Rust toolchain available is also helpful.

Service Worker / Web Worker (Experimental)

These are experimental templates for running Uzumibi applications on the client side using the browser's Service Worker API or Web Worker API.

The Rust toolchain is required for creating the Wasm.

Disclaimer

Note: The names and trademarks of each platform and software belong to their respective operating companies. This book cites these intellectual properties for introductory purposes.

Current Limitations

Multi-File Application Code

Currently, Uzumibi requires that Ruby application code be written in a single file (lib/app.rb or lib/consumer.rb).

In typical Ruby development, it's common to split code across multiple files using require or require_relative for modularization. However, in Uzumibi, these file-loading features are not available due to mruby/edge's limitations. For now, the workaround is to write all code in a single file or concatenate files using a build script.

Multi-file support is a feature that will be prioritized in future Uzumibi development.

Language Feature Limitations of mruby/edge

Since mruby/edge implements a subset of mruby, it has some limitations compared to CRuby (standard Ruby) or the full mruby:

  • Some standard libraries: Libraries that depend on OS resources such as File, IO, and Socket are not available in the WebAssembly environment.
  • Regular expressions: Regular expressions (Regexp) currently have only partial support.
  • require / load: As mentioned above, loading external files is not supported.
  • Threads / Fiber: Concurrency features are not available due to WebAssembly environment constraints.

Additionally, the Ruby features available may vary by platform in the future. We plan to document these as much as possible.

Feature Differences Between Platforms

Note: This section is updated periodically. Last updated: March 14, 2026.

While Uzumibi supports multiple platforms, external service integration features differ by platform:

FeatureCloudflareFastlySpinCloud Run
Basic HTTP processingoooo
External HTTP requests (Fetch)o---
Key-Value storeo (Durable Object)---
Message queueo (Queues)---
Static asset servingo---

Currently, Cloudflare Workers supports the most features. Support for other platforms is expected to expand progressively.

References

Uzumibi

mruby/edge

mruby

Cloudflare Workers

WebAssembly

Rust