Examples

Runnable crates in examples/ — clone and run with cargo run -p PACKAGE. All listen on http://127.0.0.1:3000 by default.

Documentation is served at resuma-docs.fly.dev (source in apps/resuma-docs, not an example crate).

ExampleCommandApp typeWhat it demonstrates
todocargo run -p example-todoResumaAppFull showcase: signals, #[server], #[island], js!, theme, backend security (guards, DTOs, service layer). Docs →
countercargo run -p example-counterResumaAppMinimal resumable counter — smallest interactive app.
flow-democargo run -p example-flow-demoFlowApp#[load], streaming SSR, #[load(stream)], deferred chunks. Docs →
flow-pagescargo run -p example-flow-pagesFlowAppFile-based routing, layouts, auto_pages, resuma routes --generate. Docs →
production templateresuma new app --template productionFlowAppSecurity stub, Dockerfile, fly.toml, /ops dashboard. Docs →

Choose an example

  • Learning Resuma?counter then todo
  • Production backend patterns?todo + Security docs
  • Multi-page site?flow-pages or resuma new --template flow
  • Bookings / calendars?resuma new --template flow-booking + Query params
  • Streaming / loaders?flow-demo
  • Full-stack + SQL?resuma new --template flow-fullstack
  • Production + ops dashboard?resuma new --template production + Resuma OS

Full app from scratch

This is the smoke-test shape we use for a fresh external project. It starts from the public CLI, then exercises routing, NavLink, #[load], #[submit], #[server], #[data], signals, client handlers, redirects, flash messages, and middleware.

cargo install resuma --version 1.0.2 --force
resuma new launchops --template basic
cd launchops

cargo check
cargo run
# open http://127.0.0.1:3000

src/main.rs pattern

use resuma::prelude::*;
use std::sync::{Mutex, OnceLock};

#[data]
struct Project {
    id: u32,
    name: String,
    owner: String,
    budget: u32,
}

#[data]
struct ProjectForm {
    name: String,
    owner: String,
    budget: String,
}

#[data]
struct PlanInput {
    topic: String,
    urgency: i32,
}

#[data]
struct PlanOutput {
    title: String,
    next_step: String,
    score: i32,
}

static PROJECTS: OnceLock<Mutex<Vec<Project>>> = OnceLock::new();

fn projects_store() -> &'static Mutex<Vec<Project>> {
    PROJECTS.get_or_init(|| {
        Mutex::new(vec![Project {
            id: 1,
            name: "Launch Analytics".into(),
            owner: "Ada".into(),
            budget: 2600,
        }])
    })
}

#[load]
async fn projects(_req: &FlowRequest) -> Vec<Project> {
    projects_store().lock().expect("project store lock").clone()
}

#[submit]
async fn add_project(form: ProjectForm, _req: &FlowRequest) -> std::result::Result<Redirect, SubmitError> {
    if form.name.trim().len() < 3 {
        return Err(SubmitError::new("Fix the project").field("name", "Use at least 3 characters"));
    }

    let budget: u32 = form.budget.trim().parse()
        .map_err(|_| SubmitError::new("Fix the project").field("budget", "Budget must be a number"))?;

    let mut projects = projects_store().lock().expect("project store lock");
    let id = projects.iter().map(|p| p.id).max().unwrap_or(0) + 1;
    projects.push(Project {
        id,
        name: form.name.trim().into(),
        owner: form.owner.trim().into(),
        budget,
    });

    Ok(redirect_with_flash("/projects", "Project created"))
}

#[server]
async fn generate_plan(input: PlanInput) -> Result<PlanOutput> {
    if input.topic.trim().is_empty() {
        return Err(ResumaError::validation("topic is required"));
    }

    let score = (input.urgency * 17).clamp(10, 100);
    Ok(PlanOutput {
        title: format!("Plan for {}", input.topic.trim()),
        next_step: "Create a focused implementation checklist".into(),
        score,
    })
}

#[middleware]
async fn request_log(req: FlowRequest) -> resuma::Result<FlowRequest> {
    println!("[launchops] {} {}", req.method, req.path);
    Ok(req)
}

fn nav() -> View {
    view! {
        <nav>
            <NavLink href="/" activeClass="active">"Dashboard"</NavLink>
            <NavLink href="/projects" activeClass="active">"Projects"</NavLink>
            <NavLink href="/workbench" activeClass="active">"Workbench"</NavLink>
        </nav>
    }
}

#[component]
fn ProjectsPage() {
    let rows = use_projects_load();
    let flash = current_request().and_then(|req| flash_message(&req));

    view! {
        <main>
            {nav()}
            <h1>"Projects"</h1>
            {flash.map(|msg| view! { <p class="flash">{msg}</p> }).unwrap_or(View::Empty)}
            <section>
                {rows.iter().map(|project| view! {
                    <article key={project.id.to_string()}>
                        <h2>{project.name.clone()}</h2>
                        <p>{format!("Owner: {} - Budget: ${}", project.owner, project.budget)}</p>
                    </article>
                }).collect::<Vec<_>>()}
            </section>
            <Form submit={add_project}>
                <input name="name" placeholder="Mobile dashboard" />
                <input name="owner" placeholder="Ada" />
                <input name="budget" placeholder="3200" inputmode="numeric" />
                <button type="submit">"Create"</button>
            </Form>
        </main>
    }
}

#[component]
fn WorkbenchPage() {
    let urgency = signal(3_i32);
    let topic = signal("release readiness".to_string());
    let result = signal("No plan generated yet".to_string());

    view! {
        <main>
            {nav()}
            <h1>"Workbench"</h1>
            <p>"Urgency: " {urgency}</p>
            <button type="button" onClick={js! {
                state.urgency.set(Math.max(1, state.urgency.value - 1));
            }}>"-"</button>
            <button type="button" onClick={js! {
                state.urgency.set(Math.min(5, state.urgency.value + 1));
            }}>"+"</button>

            <input value={topic.get()} onInput={js! { state.topic.set(event.target.value); }} />
            <button type="button" onClick={js! {
                const plan = await __resuma.action("generate_plan", [{
                    topic: state.topic.value,
                    urgency: state.urgency.value,
                }]);
                state.result.set(plan.title + ": " + plan.next_step + " (" + plan.score + ")");
            }}>"Ask server"</button>
            <p>{result}</p>
        </main>
    }
}

#[component]
fn DashboardPage() {
    view! {
        <main>
            {nav()}
            <h1>"LaunchOps"</h1>
            <p>"Flow routes, loaders, submits, server actions, signals, SPA navigation, and SSR."</p>
        </main>
    }
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    FlowApp::new()
        .with_title("LaunchOps")
        .component("/", DashboardPage)
        .component("/projects", ProjectsPage)
        .component("/workbench", WorkbenchPage)
        .serve(FlowServeOptions::default())
        .await
}

CLI templates

resuma new my-app --template basic scaffolds like a minimal counter. --template todo copies the todo example (main + security + todo_store).