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.
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:
| File | Purpose |
|---|---|
Cargo.toml | Rust dependencies, crate metadata, and Wasm release-profile settings |
wrangler.toml or wrangler.jsonc | Worker name, build command, bindings, and deployment settings |
src/lib.rs | Rust 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:
| Method | Path | Behavior |
|---|---|---|
POST | /:country | Accepts {"city": "Paris"} and stores the value in KV |
GET | /:country | Reads 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_namespacesexists inwrangler.tomlorwrangler.jsonc.- The binding is named exactly
cities. wrangler devis 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
POSTrequests. - 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:
- Install Rust, the Wasm target,
cargo-generate, and Wrangler. - Generate a template with
cargo generate cloudflare/workers-rs. - Write the entrypoint in
src/lib.rswith#[event(fetch)]. - Use
Routerto organize HTTP routes. - Declare bindings in Wrangler configuration and access them through APIs such as
ctx.kv("..."). - Test with
npx wrangler devand deploy withnpx 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 GitHub ↗
Exploring technology and life's wisdom