DEV Community

Alex Spinov
Alex Spinov

Posted on

Leptos Has a Free API: Build Full-Stack Web Apps in Rust With Fine-Grained Reactivity

Leptos is a Rust framework for building full-stack web applications with fine-grained reactivity — similar to SolidJS but compiled to WebAssembly for near-native performance.

Why Leptos Matters

JavaScript frameworks ship a runtime to the browser. Leptos compiles to WebAssembly, delivering reactive UIs with zero JS runtime overhead. It also supports server-side rendering with hydration.

What you get for free:

  • Fine-grained reactivity (only updates what changed — no virtual DOM)
  • Full-stack: SSR + client hydration in one Rust codebase
  • WebAssembly compilation for near-native browser performance
  • Type safety across client and server
  • Built-in router, forms, and server functions
  • Component size: ~5KB gzipped for a counter app

Quick Start

# Install
cargo install cargo-leptos

# Create project
cargo leptos new --git leptos-rs/start
cd my-project

# Development
cargo leptos watch

# Build for production
cargo leptos build --release
Enter fullscreen mode Exit fullscreen mode

Reactive Signals

use leptos::*;

#[component]
fn Counter() -> impl IntoView {
    let (count, set_count) = create_signal(0);
    let double = move || count() * 2;

    view! {
        <button on:click=move |_| set_count.update(|n| *n += 1)>
            "Count: " {count}
        </button>
        <p>"Double: " {double}</p>
    }
}
Enter fullscreen mode Exit fullscreen mode

Server Functions: Full-Stack in One File

use leptos::*;

// This runs on the server — automatically creates an API endpoint
#[server(GetUsers, "/api")]
pub async fn get_users() -> Result<Vec<User>, ServerFnError> {
    let pool = use_context::<PgPool>().unwrap();
    let users = sqlx::query_as!(User, "SELECT * FROM users")
        .fetch_all(&pool)
        .await?;
    Ok(users)
}

// This runs in the browser — calls the server function via fetch
#[component]
fn UserList() -> impl IntoView {
    let users = create_resource(|| (), |_| async { get_users().await });

    view! {
        <Suspense fallback=move || view! { <p>"Loading..."</p> }>
            {move || users.get().map(|result| match result {
                Ok(users) => view! {
                    <ul>
                        {users.into_iter().map(|u| view! {
                            <li>{u.name}</li>
                        }).collect_view()}
                    </ul>
                },
                Err(e) => view! { <p>{format!("Error: {e}")}</p> }.into_view(),
            })}
        </Suspense>
    }
}
Enter fullscreen mode Exit fullscreen mode

Router

use leptos::*;
use leptos_router::*;

#[component]
fn App() -> impl IntoView {
    view! {
        <Router>
            <main>
                <Routes>
                    <Route path="/" view=HomePage />
                    <Route path="/users" view=UserList />
                    <Route path="/users/:id" view=UserProfile />
                    <Route path="/*" view=NotFound />
                </Routes>
            </main>
        </Router>
    }
}

#[component]
fn UserProfile() -> impl IntoView {
    let params = use_params_map();
    let id = move || params.with(|p| p.get("id").cloned().unwrap_or_default());

    view! { <p>"User ID: " {id}</p> }
}
Enter fullscreen mode Exit fullscreen mode

Performance Comparison

Framework Bundle Size (gzip) Lighthouse Score Reactivity
React ~45KB 85-95 Virtual DOM
SolidJS ~7KB 95-100 Fine-grained
Leptos ~5KB 95-100 Fine-grained (Wasm)
Svelte ~2KB 95-100 Compiled

Useful Links


Building high-performance web scrapers? Check out my developer tools on Apify for ready-made actors, or email spinov001@gmail.com for custom solutions.

Top comments (0)