A fork of workers-rs that provides a safer and more ergonomic way to write Cloudflare Workers in Rust. It supports the following features:
-  httpcrate for HTTP types, instead of using the custom types ofworkers-rs. This allows you to use Axum or any other HTTP framework.
-  Removed all unsafecode, and replaced it with safe wrappers.
-  All types are SendandSync, so you can use them in async functions, unlike types inworkers-rs.
-  async/.awaitsupport for all event types.
use betterworker::prelude::*;
#[event(fetch)]
pub async fn main(req: Request<Body>, _env: Env, _ctx: Context) -> Result<Response<Body>, Error> {
    let cf = req.extensions().get::<Cf>().unwrap();
    console_log!(
        "{} {}, located at: {:?}, within: {}",
        req.method().to_string(),
        req.uri().path(),
        cf.coordinates().unwrap_or_default(),
        cf.region().unwrap_or("unknown region".into())
    );
    Ok(Response::new(Body::from("Hello, world!")))
}The project uses wrangler version 2.x for running and publishing your Worker.
Get the Rust worker project template manually, or run the following command:
npm init cloudflare project_name worker-rust
cd project_nameYou should see a new project layout with a src/lib.rs. Start there! Use any local or remote crates
and modules (as long as they compile to the wasm32-unknown-unknown target).
Once you're ready to run your project:
First check that the wrangler version is 2.x
npx wrangler --versionThen, run your worker
npx wrangler devFinally, go live:
# configure your routes, zones & more in your worker's `wrangler.toml` file
npx wrangler publishIf you would like to have wrangler installed on your machine, see instructions in wrangler repository.
All "bindings" to your script (Durable Object & KV Namespaces, Secrets, and Variables) are
accessible from the env parameter provided to the entrypoint (main in this example).
use betterworker::{
    http::{Method, StatusCode},
    prelude::*,
};
#[event(fetch)]
pub async fn main(req: Request<Body>, env: Env, _ctx: Context) -> Result<Response<Body>, Error> {
    match (req.method().clone(), req.uri().path()) {
        (Method::GET, "/durable") => {
            let namespace = env.durable_object("CHATROOM")?;
            let stub = namespace.id_from_name("A")?.get_stub()?;
            stub.fetch_with_str("/messages").await
        }
        (Method::GET, "/secret") => Ok(Response::new(Body::from(
            env.secret("CF_API_TOKEN")?.to_string(),
        ))),
        (Method::GET, "/var") => Ok(Response::new(Body::from(
            env.var("BUILD_NUMBER")?.to_string(),
        ))),
        (Method::POST, "/kv") => {
            let kv = env.kv("SOME_NAMESPACE")?;
            kv.put("key", "value")?.execute().await?;
            Ok(Response::new(Body::empty()))
        }
        (_, _) => Ok(Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::empty())
            .unwrap()),
    }
}For more information about how to configure these bindings, see:
- https://developers.cloudflare.com/workers/cli-wrangler/configuration#keys
- https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-bindings
To define a Durable Object using the worker crate you need to implement the DurableObject trait
on your own struct. Additionally, the #[durable_object] attribute macro must be applied to both
your struct definition and the trait impl block for it.
#![feature(async_fn_in_trait)]
use betterworker::prelude::*;
use betterworker::wasm_bindgen;
#[durable_object]
pub struct Chatroom {
    users: Vec<User>,
    messages: Vec<Message>,
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`
}
#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            users: vec![],
            messages: vec![],
            state: state,
            env,
        }
    }
    async fn fetch(&mut self, _req: Request<Body>) -> Result<Response<Body>, Error> {
        // do some work when a worker makes a request to this DO
        Response::ok(&format!("{} active users.", self.users.len()))
    }
}You'll need to "migrate" your worker script when it's published so that it is aware of this new
Durable Object, and include a binding in your wrangler.toml.
- Include the Durable Object binding type in you wrangler.tomlfile:
# ...
[durable_objects]
bindings = [
  { name = "CHATROOM", class_name = "Chatroom" } # the `class_name` uses the Rust struct identifier name
]
[[migrations]]
tag = "v1" # Should be unique for each entry
new_classes = ["Chatroom"] # Array of new classes- For more information about migrating your Durable Object as it changes, see the docs here: https://developers.cloudflare.com/workers/learning/using-durable-objects#durable-object-migrations-in-wranglertoml
As queues are in beta you need to enable the queue feature flag.
Enable it by adding it to the worker dependency in your Cargo.toml:
worker = {version = "...", features = ["queue"]}use betterworker::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug, Clone, Deserialize)]
pub struct MyType {
    foo: String,
    bar: u32,
}
// Consume messages from a queue
#[event(queue)]
pub async fn main(message_batch: MessageBatch<MyType>, env: Env, _ctx: Context) -> Result<(), Error> {
    // Get a queue with the binding 'my_queue'
    let my_queue = env.queue("my_queue")?;
    // Deserialize the message batch
    let messages = message_batch.messages()?;
    // Loop through the messages
    for message in messages {
        // Log the message and meta data
        console_log!(
            "Got message {:?}, with id {} and timestamp: {}",
            message.body,
            message.id,
            message.timestamp.to_string()
        );
        // Send the message body to the other queue
        my_queue.send(&message.body).await?;
    }
    // Retry all messages
    message_batch.retry_all();
    Ok(())
}As D1 databases are in alpha, you'll need to enable the d1 feature on the worker crate.
worker = { version = "x.y.z", features = ["d1"] }use betterworker::{prelude::*, http::Method};
use serde::Deserialize;
#[derive(Deserialize)]
struct Thing {
	thing_id: String,
	desc: String,
	num: u32,
}
#[event(fetch)]
pub async fn main(req: Request<Body>,	env: Env, _ctx: Context) -> Result<Response<Body>, Error> {
    match (req.method().clone(), req.uri().path()) {
        (Method::GET, route) => {
			let d1 = env.d1("things-db")?;
			let statement = d1.prepare("SELECT * FROM things WHERE thing_id = ?1");
			let query = statement.bind(route)?;
			let result = query.first::<Thing>(None).await?;
            let serialized = serde_json::to_string(&result)?;
			match result {
				Some(thing) => Ok(Response::new(Body::new(serialized))),
				None => Ok(Response::builder()
                    .status(404)
                    .body(Body::empty())
                    .unwrap()),
			}
        }
        _ => Ok(Response::builder()
            .status(404)
            .body(Body::empty())
            .unwrap()),
    }
}