Wisp is in pre-release, self-hosted since GitHub suspended us. Install it, or see the changelog.

Wisp

Wisp: A Fast, Fun Web Framework for Rust

cargo install --git https://wisp.ar0.eu/ --tag v1.0.0-pre.3 wisp-web

Learn Wisp API Reference

src/routes/+page.wisp
---
#[model]
struct Todo {
    #[validate(len = 1..=100)]
    text: String,
}

static TODOS: Table<Todo> = Table::saved();

#[action]
fn add(todo: Todo) {
    TODOS.add(todo)?;
}

#[action]
fn remove(id: u64) {
    TODOS.remove(id)?;
}
---

<title>Todos (${TODOS.len()})</title>
<meta name="description" content="A todo list that saves its rows and checks what you add.">
<h1>Todos</h1>

<form action="?/add" fields />

{#each TODOS.all() as todo}
  <p> ${todo.text}
    <button action="?/remove&id=${todo.id}">
      Remove
    </button>
  </p>
{/each}
localhost:3000

Todos (3)

  • Buy milk
  • Write the docs
  • Learn to code

A Page Is One File

Model, table, actions and markup. The form writes its own inputs and errors.

Pages, forms and validation compile to Rust at build time. A request runs fixed text and a few function calls.

Benchmarks

Same machine, same routes, release builds. The charts show every contender, also where Wisp loses.

The charts use the TechEmpower plaintext and JSON tests. Our script follows the TechEmpower method. This is not an official TechEmpower result. Plaintext uses pipeline depth 16. Each bar is the highest median of at least 5 valid runs over the connection counts.

Plaintextreq/s, best of 256 to 16,384 connections
Wisp 4,202,276
Actix Web 1,773,586
Axum 1,205,927
Hono on Bun 229,660
Fastify 176,034
Express 121,999
Hono on Node.js 112,070
Nuxt 92,976
SvelteKit 33,042
Next.js 10,902
JSONreq/s, best of 16 to 512 connections
Wisp 460,034
Actix Web 260,653
Axum 228,588
Hono on Bun 213,293
Fastify 99,949
Nuxt 85,775
Express 80,687
Hono on Node.js 59,993
SvelteKit 28,945
Next.js 5,062

Dates, machines and the full tables are on the benchmarks page.

Forms Without JavaScript

A bad value sends the form back with the error next to its input and your text kept. With JavaScript on, the page updates in place.

#[model]
struct User {
    #[unique]
    email: Email,
    password: Password,
}
pub static USERS: Table<User> = Table::saved();
---
#[action]
fn signup(email: Email, password: Password) {
    cx.signup(User { email, password }).await?;
    redirect("/me")
}
---

<form action="?/signup" fields>
  <button>Sign up</button>
</form>

Reactivity in the Same File

The top block is Rust and runs on the server. The count is JavaScript state in the browser.

---
let name = cx.query_or("name", "world".to_string());
---

<h1>Hello, ${name}!</h1>

<button on:click="count++">Clicked {:count} times</button>

Fewer Tokens

Wisp infers types and uses conventions over config, so code stays short. The table counts the tokens of one small app in each stack.

StackRelative sizeTokensFiles
Wisp 477 7
Nuxt 1,043 8
SvelteKit 1,134 9
Next.js 1,146 8
Express 1,297 8
React 1,599 9
Axum 1,803 7
Actix Web 1,878 7
claude mcp add wisp -- wisp mcp

How Tokens Are Counted

Editors

The Wisp language server gives highlighting, completion, hover docs, diagnostics and formatting in each editor below.

One Binary

The app, its styles and static files become one binary. You can also build a container, static HTML, or a target for an edge or serverless host.

wisp build
wisp build --static
wisp deploy init docker
wisp build --target cloudflare
wisp deploy init cloudflare

Testing

Every file is audited within three days of a change. Over a thousand tests run on each change, and the parsers are fuzzed. Wisp is new and no third party has audited it.

What Is Hardened

Install Wisp

cargo install --git https://wisp.ar0.eu/ --tag v1.0.0-pre.3 wisp-web

Learn Wisp

Type to search every docs page and heading.