skyzen 0.1.1

A fast, ergonomic HTTP framework that works everywhere
docs.rs failed to build skyzen-0.1.1
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: skyzen-0.1.0

Skyzen

crates.io doc.rs License Coverage

A fast, ergonomic HTTP framework for Rust focused on native servers and Cloudflare-compatible edge/serverless platforms.

Features

  • Portable core — Write handlers against Kv, Storage, Queue, and Db instead of provider SDK types
  • Stable runtimes today — Native servers and WinterCG/Cloudflare Workers share the same handler model
  • Provider extensions — Opt into raw/provider-specific APIs such as CfD1, queue/scheduled event handlers, and Durable Object capabilities only when you need more than the portable minimum
  • Extractor/Responder pattern — Type-safe request parsing and response generation via function arguments and return types
  • Tree-based routing — Fast, composable routing with path parameters, HTTP method matching, and nested routes
  • WebSocket support — Unified WebSocket API across native (async-tungstenite) and WASM (WebSocketPair)
  • OpenAPI generation — Automatic API documentation from annotated handlers
  • #[skyzen::main] — One macro for both native (Tokio + Hyper + logging + graceful shutdown) and WASM (WinterCG fetch export)
  • Unified CLIskyzen new/dev/deploy scaffolds projects, runs native watch/restart, and orchestrates Cloudflare-first deployment flows

Getting Started

[dependencies]
skyzen = "0.1"

The simplest Skyzen app:

use skyzen::routing::{CreateRouteNode, Route, Router};

#[skyzen::main]
fn main() -> Router {
    Route::new((
        "/".at(|| async { "Hello, World!" }),
        "/health".at(|| async { "OK" }),
    ))
    .build()
}

Run with cargo run and open the address printed in the startup log, or pin a port explicitly with cargo run -- --port 8787.

Extractors & Responders

Pull data from requests with extractors:

use skyzen::utils::Json;
use skyzen::routing::Params;

async fn create_user(
    params: Params,
    Json(body): Json<CreateUserRequest>,
) -> Result<Json<User>> {
    // params and body are automatically extracted
}

Return anything that implements Responder:

async fn handler() -> impl Responder {
    Json(data)  // or String, &str, Response, Result<T>, etc.
}

Routing

Skyzen's routing system is built around Route::new() and intuitive path methods:

use skyzen::routing::{CreateRouteNode, Route, Router};

fn router() -> Router {
    Route::new((
        "/".at(|| async { "Home" }),
        "/users/{id}".at(|params: Params| async move {
            let id = params.get("id")?;
            Ok(format!("User: {id}"))
        }),
        "/posts".get(list_posts),
        "/posts".post(create_post),
        "/posts/{id}".put(update_post),
        "/posts/{id}".delete(delete_post),
    ))
    .build()
}

WebSocket

use skyzen::routing::{CreateRouteNode, Route};
use skyzen::websocket::WebSocketUpgrade;

Route::new((
    "/ws".ws(|mut socket| async move {
        while let Some(Ok(message)) = socket.next().await {
            if let Some(text) = message.into_text() {
                let _ = socket.send_text(text).await;
            }
        }
    }),
))

WebSocket works on both native (via async-tungstenite) and WASM (via WebSocketPair).

Platform Comparison

Portable handlers run against the same capability wrappers. Native and Cloudflare automatic wiring are built in today; AWS and Azure provider crates remain available as infrastructure backends, but runtime parity for those targets is not part of the finished scope.

Service Native Cloudflare AWS Azure Test
Key-Value skyzen-redis CfKv DynamoKv CosmosKv InMemoryKv
Object Storage skyzen-s3 CfR2 S3Storage AzureBlob InMemoryStorage
Message Queue CfQueue SqsQueue ServiceBusQueue InMemoryQueue
Portable SQL Db via sqlx Db via D1 planned wiring planned wiring

Provider-specific escape hatches remain available when you need more than the portable minimum:

  • Cloudflare raw SQL and stateful primitives: CfD1, DurableKv, DurableDb, Alarm, Durable Objects

See the Services Guide for how to write platform-agnostic handlers and switch between backends. For per-object SQL state that runs on both native and Cloudflare, see the Durable Object + SQL Guide.

Services Abstraction

Skyzen provides portable capability wrappers through skyzen-services. Application code depends on those wrappers, not on provider SDK types:

use skyzen_services::{Db, Kv, Storage};

async fn handler(kv: Kv, storage: Storage, db: Db) -> Result<Json<Data>> {
    let cached = kv.get_json::<Data>("cache:key").await?;
    let file = storage.get("assets/logo.png").await?;
    let users = db
        .query("SELECT id, name FROM users")
        .fetch_all::<User>()
        .await?;
    Ok(Json(cached.unwrap_or_default()))
}

Wire different backends depending on your deployment target. The wrapper type stays the same:

// Native: Redis + S3
let kv = Kv::new(Redis::connect("redis://localhost:6379").await?);
let storage = Storage::new(S3Storage::from_env("my-bucket"));

// Cloudflare: Workers KV + R2
let kv = Kv::new(CfKv::from_env(&env, "CACHE")?);
let storage = Storage::new(CfR2::from_env(&env, "UPLOADS")?);

// Testing: In-memory mocks
let kv = Kv::new(InMemoryKv::new());
let storage = Storage::new(InMemoryStorage::new());

The #[skyzen::main] Macro

For HTTP servers, #[skyzen::main] provides:

  • Pretty logging with tracing (respects RUST_LOG)
  • Graceful shutdown on Ctrl+C
  • CLI overrides for host/port (--port, --host, --listen)
  • Tokio + Hyper runtime configured and ready
#[skyzen::main]
fn main() -> Router {
    router()
}

Disable the default logger to configure your own:

#[skyzen::main(default_logger = false)]
async fn main() -> Router {
    tracing_subscriber::fmt().init();
    router()
}

WASM Deployment

For serverless edge platforms, use a lib crate with cdylib:

[lib]
crate-type = ["cdylib", "rlib"]
#[skyzen::main]
fn app() -> Router {
    router()
}

On WASM targets, #[skyzen::main] exports a WinterCG-compatible fetch handler for Cloudflare Workers, Deno Deploy, and other edge runtimes.

See the Deployment Guide for full setup instructions.

CLI

The skyzen CLI scaffolds projects, runs native watch/restart development, and orchestrates deployment:

skyzen new my-app --template api
skyzen new jobs-app --template serverless-events
skyzen new room-app --template durable-realtime
skyzen doctor                          # Check toolchain
skyzen dev                             # Native watch + restart
skyzen dev --provider cloudflare       # Wrangler-driven Cloudflare dev
skyzen deploy --provider cloudflare    # Deploy to Workers

Configure platforms via Skyzen.toml. For Cloudflare, Skyzen generates .skyzen/gen/wrangler.toml automatically; users do not hand-maintain wrangler.toml.

Cloudflare Event Handlers

Skyzen also supports Cloudflare-specific queue and scheduled entrypoints:

#[cfg(target_arch = "wasm32")]
#[skyzen::queue]
async fn queue(
    batch: skyzen_cloudflare::CfQueueBatch,
    env: skyzen::runtime::wasm::Env,
    ctx: skyzen_cloudflare::CfQueueContext,
) -> Result<(), skyzen_cloudflare::CfEventError> {
    batch.ack_all()?;
    Ok(())
}

#[cfg(target_arch = "wasm32")]
#[skyzen::scheduled]
async fn scheduled(
    event: skyzen_cloudflare::CfScheduledEvent,
    env: skyzen::runtime::wasm::Env,
    ctx: skyzen_cloudflare::CfScheduleContext,
) -> Result<(), skyzen_cloudflare::CfEventError> {
    Ok(())
}

For stateful Cloudflare-specific workflows, use #[skyzen::durable_object] with the DurableObject trait.

Custom Server

For advanced scenarios like embedding Skyzen or using a custom runtime:

use skyzen::{Server, Endpoint};
use skyzen_hyper::Hyper;

async fn run_custom() {
    let router = router().build();
    let executor = MyExecutor::new();
    let connections = my_tcp_listener();

    Hyper.serve(
        executor,
        |error| eprintln!("Connection error: {error}"),
        connections,
        router,
    ).await;
}

OpenAPI Documentation

Generate API docs automatically:

#[skyzen::openapi]
async fn get_user(params: Params) -> Result<Json<User>> {
    // Handler implementation
}

fn router() -> Router {
    Route::new(("/users/{id}".at(get_user),))
        .enable_api_doc()  // Serves docs at /api-docs
        .build()
}

Workspace Crates

Crate Description README
skyzen Main framework — routing, middleware, extractors, responders, runtime README
skyzen-core Foundational traits (Extractor, Responder, Server), no_std support README
skyzen-hyper Hyper server backend README
skyzen-macros Procedural macros (#[skyzen::main], #[skyzen::openapi], etc.) README
skyzen-services Portable service traits and extractors (Kv, Storage, Queue, Db) README
skyzen-test Mock services, TestClient, assertions, snapshot testing README
skyzen-redis Redis KeyValueStore implementation README
skyzen-s3 S3-compatible ObjectStorage implementation README
skyzen-cloudflare Cloudflare Workers implementations (KV, R2, Queues, D1, Durable Objects) README
skyzen-aws AWS infrastructure backends (DynamoDB, SQS, S3) README
skyzen-azure Azure infrastructure backends (Cosmos DB, Blob Storage, Service Bus) README
skyzen-cli Unified CLI for local emulation and deployment README

Guides

License

MIT or Apache-2.0, at your option.