cloudflare · · 6 min read

Build Cloudflare Workers with Rust: A Practical workers-rs Guide

Build a Rust-powered Cloudflare Worker from scratch with workers-rs, Wrangler, WebAssembly, and Workers KV, then deploy a small country-to-city API at the edge.

Mttao Mttao @mttao 1,135 words 中文 →
Build Cloudflare Workers with Rust: A Practical workers-rs Guide

Why Write Cloudflare Workers in Rust

Cloudflare Workers is an edge-computing runtime that runs code across Cloudflare’s global network, close to users. JavaScript and TypeScript are the most familiar path for many Workers projects, but workers-rs lets you write the Worker itself in Rust and deploy it as WebAssembly to the same runtime.

That path is a good fit when:

  • You want Rust’s type system, error handling, and memory safety at the edge.
  • You are building protocol parsing, data transformation, request signing, lightweight gateways, or other system-leaning logic.
  • You want direct access to Workers bindings such as KV, R2, D1, Queues, Durable Objects, or Workers AI.
  • You are comfortable with a Wasm build pipeline in exchange for stronger compile-time constraints.

This guide builds a small but complete example: a country-to-city API backed by Workers KV.

The commands and key APIs below were checked against Cloudflare’s official documentation and the workers-rs repository on 2026-07-30. Wrangler, workers-rs, and the Workers runtime continue to evolve, so use the current official docs as the source of truth for new projects.

Prepare the Development Environment

You need Rust, Node.js, and Wrangler. The workers-rs template also uses cargo-generate, and the Worker is compiled for the wasm32-unknown-unknown target:

# Install or update Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add the Wasm target
rustup target add wasm32-unknown-unknown

# Install cargo-generate
cargo install cargo-generate

Wrangler can be run through npx, so a global install is optional:

npx wrangler --version

The first time you run wrangler dev or wrangler deploy, Wrangler will guide you through Cloudflare authentication. If your environment has no browser UI, use Wrangler’s login documentation for the alternative flow.

Create a workers-rs Project

Generate a project from the official template:

cargo generate cloudflare/workers-rs

Choose a template when prompted. For a first project, template/hello-world-http is a good starting point. Use a project name such as:

rust-kv-demo

The generated project typically includes:

FilePurpose
Cargo.tomlRust dependencies, crate metadata, and Wasm release-profile settings
wrangler.toml or wrangler.jsoncWorker name, build command, bindings, and deployment settings
src/lib.rsRust entrypoint for the Worker

Enter the project directory:

cd rust-kv-demo

The template uses worker-build to bundle the Rust/Wasm output into a Worker that Wrangler can run and deploy. You usually do not need to write JavaScript glue code yourself.

Verify Hello World

The generated src/lib.rs will look similar to this:

use worker::*;

#[event(fetch)]
async fn main(_req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    Response::ok("Hello, World!")
}

Run it locally:

npx wrangler dev

Open http://localhost:8787. If you see Hello, World!, the Rust to Wasm to Workers development loop is working. When you edit src/lib.rs, Wrangler rebuilds the Worker for local testing.

Example Goal: A Country-to-City KV API

The demo API exposes two routes:

MethodPathBehavior
POST/:countryAccepts {"city": "Paris"} and stores the value in KV
GET/:countryReads the city for that country from KV

It is intentionally small, but it touches the pieces you need in real Workers projects: Request, Response, Env, Router, route parameters, JSON parsing, and KV bindings.

Create a KV Namespace

Create a KV namespace named cities:

npx wrangler kv namespace create cities

Wrangler returns configuration output. Copy the id into your Worker configuration.

For wrangler.toml:

[[kv_namespaces]]
binding = "cities"
id = "your-namespace-id"

For wrangler.jsonc:

{
  "kv_namespaces": [
    {
      "binding": "cities",
      "id": "your-namespace-id"
    }
  ]
}

The binding name matters. The Rust code will access the namespace with:

ctx.kv("cities")?

Binding names are case-sensitive, so keep the configuration and the Rust code aligned.

Add Serde

The API parses JSON, so add serde:

cargo add serde --features derive

The template already includes the worker dependency. Your Cargo.toml should contain something close to:

[dependencies]
worker = "0.6"
serde = { version = "1", features = ["derive"] }

Treat the worker version as illustrative. For a new project, use the version generated by the template or the current crate version.

Complete src/lib.rs

Replace src/lib.rs with:

use serde::{Deserialize, Serialize};
use worker::*;

#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
    let router = Router::new();

    #[derive(Serialize, Deserialize, Debug)]
    struct Country {
        city: String,
    }

    router
        .post_async("/:country", |mut req, ctx| async move {
            let country = ctx.param("country").unwrap();
            let city = match req.json::<Country>().await {
                Ok(c) => c.city,
                Err(_) => String::from(""),
            };

            if city.is_empty() {
                return Response::error("Bad Request", 400);
            }

            match ctx.kv("cities")?.put(country, &city)?.execute().await {
                Ok(_) => Response::ok(city),
                Err(_) => Response::error("Bad Request", 400),
            }
        })
        .get_async("/:country", |_req, ctx| async move {
            if let Some(country) = ctx.param("country") {
                match ctx.kv("cities")?.get(country).text().await? {
                    Some(city) => Response::ok(city),
                    None => Response::error("Country not found", 404),
                }
            } else {
                Response::error("Bad Request", 400)
            }
        })
        .run(req, env)
        .await
}

The important pieces are:

  • #[event(fetch)] declares the HTTP request entrypoint.
  • Router::new() maps methods and paths to handlers.
  • ctx.param("country") reads the route parameter from /:country.
  • ctx.kv("cities") retrieves the configured Workers KV namespace.

put(...).execute().await writes to KV, and get(...).text().await reads a text value. Invalid JSON or an empty city returns 400; a missing country returns 404.

Test Locally

Start the development server:

npx wrangler dev

Write a value:

curl --json '{"city": "Paris"}' http://localhost:8787/France

Read it back:

curl http://localhost:8787/France

Expected response:

Paris

If you get a KV binding error, check three things first:

  • kv_namespaces exists in wrangler.toml or wrangler.jsonc.
  • The binding is named exactly cities.
  • wrangler dev is loading the configuration file you edited.

Deploy to Cloudflare

Deploy when the local version works:

npx wrangler deploy

After deployment, use your *.workers.dev URL or a custom domain. If this is your first workers.dev deployment, DNS propagation can take a short moment.

Production Notes

Control Wasm Size

The workers-rs template configures release-profile optimizations such as lto, strip, and codegen-units. Cloudflare’s Rust documentation also explains that worker-build handles the Wasm bundling and optimization path.

You still need to be careful with dependencies. Avoid crates that cannot compile to wasm32-unknown-unknown, and keep the dependency graph small enough that deployment and startup stay predictable.

Choose Wasm-Compatible Crates

Many Rust crates work on Workers, but not every crate is Wasm-compatible out of the box. Libraries that depend on native threads, the local filesystem, native TLS, or blocking network calls need extra scrutiny.

Cloudflare maintains a Supported crates page. For crates such as serde, HTTP clients, and time libraries, check whether Wasm-specific features are required.

KV Is Only the First Binding

KV is a useful starting point. workers-rs can also access other Workers platform bindings:

  • R2 for object storage.
  • D1 for relational data.
  • Durable Objects for stateful coordination and real-time collaboration.
  • Queues for asynchronous work.
  • Workers AI for edge AI calls.

Some bindings require additional features or configuration, so read the workers-rs repository and the relevant Cloudflare docs before adding them.

Do Not Leave Demo Error Handling in Production

The example keeps error handling intentionally simple. For a production API, consider:

  • Structured JSON error responses.
  • Separate handling for JSON parse errors, missing parameters, KV failures, and configuration mistakes.
  • Body size, field length, and character restrictions for POST requests.
  • Authentication, rate limiting, or origin checks for public endpoints.

Edge APIs sit close to users, which also means bad requests arrive quickly. Tighten the API boundary early.

Summary

The core workers-rs workflow is straightforward:

  1. Install Rust, the Wasm target, cargo-generate, and Wrangler.
  2. Generate a template with cargo generate cloudflare/workers-rs.
  3. Write the entrypoint in src/lib.rs with #[event(fetch)].
  4. Use Router to organize HTTP routes.
  5. Declare bindings in Wrangler configuration and access them through APIs such as ctx.kv("...").
  6. Test with npx wrangler dev and deploy with npx wrangler deploy.

If your team already works in Rust, workers-rs is a natural way to build edge applications: you keep Rust’s type-driven development style while integrating directly with Cloudflare’s global runtime and platform bindings.

References

Mttao

Mttao GitHub ↗

Exploring technology and life's wisdom

Related Posts

View all →
  1. 01 Rust target Directory Too Large? A Practical Cleanup and Optimization Guide rust· Jul 28, 2026
  2. 02 Rust clap in Practice: Build a Production-Ready CLI rust· Jul 27, 2026
  3. 03 The Complete Cloudflare Wrangler Guide: From Local Development to Global Deployment Cloudflare· Aug 15, 2025

/ Comments