Skip to content
HN On Hacker News ↗

GitHub - tokio-rs/topcoat: A batteries-included framework for building web apps

▲ 134 points • 48 comments • by wertyk • 4mo ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

3 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 3 of 3
SEGMENTS · AI 0 of 3
WORD COUNT 712
PEAK AI % 6% · §1
Analyzed
Jul 17
backend: pangram/v3.3
Segments scanned
3 windows
avg 237 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 712 words · 3 segments analyzed

Human AI-generated
§1 Human · 6%

The full full-stack framework for Rust

Topcoat is a modular, batteries-included Rust framework for building fullstack apps. It prioritizes simplicity and productivity. See the Getting started guide to set up a new project.

Early-stage and experimental. Expect breaking changes.

use topcoat::{ Result, router::{Router, RouterBuilderDiscoverExt, page}, view::{component, view}, };

#[tokio::main] async fn main() { topcoat::start(Router::builder().discover().build()).await.unwrap(); }

#[page("/")] async fn home() -> Result { view! { <!DOCTYPE html> <html> <body> hello(name: "World") </body> </html> } }

#[component] async fn hello(name: &str) -> Result { view! { <h1>"Hello, " (name) "!"</h1> } } What makes Topcoat different Client reactivity without the boilerplate Topcoat renders all markup on the server: components can be async and query the database directly, eliminating all the traditional boilerplate needed for a separate API layer. Interactivity does not have to cost a round-trip, though. A $(...) expression is ordinary type-checked Rust that Topcoat evaluates on the server for the initial render and also translates to JavaScript, so it re-runs instantly in the browser. No wasm bundle, no client build step: view! { signal open = false;

// Runs entirely in the browser; no server round-trip. <button @click=$(|_e| open.set(!open.get()))>"What is Topcoat?"</button> <p :hidden=$(!open.get())>"A fullstack Rust framework."</p> } When an update does need the server, like fresh search results, mark the component as a #[shard]. Topcoat re-renders it on the server whenever one of its $(...) arguments changes and swaps the new HTML in place: #[component] async fn search() -> Result { view! { signal query = String::new();

<input @input=$(|e: Event| query.set(e.target.value))>

// Updates as the user types.

§2 Human · 1%

search_results(query: $(query.get())) } }

#[shard] async fn search_results(cx: &Cx, query: String) -> Result { view! { <ul> // Your own server-side code, like a database query: for product in search_products(cx, &query).await? { <li>(product.name)</li> } </ul> } } Powerful, unsurprising HTML templates The view! macro stays true to HTML and Rust. Use familiar Rust control flow as part of your templates: view! { <nav> for item in nav_items { <a href=(item.url) if item.url == current_path { aria-current="page" class="active" } > (item.label) </a> } </nav> } Use the topcoat fmt CLI command to automatically format view! snippets (and other macros) across your codebase. Module-based routing Topcoat can optionally infer your route tree from your app's module structure (without a build step): src/ |-- app.rs -> / (and the root <html> layout) `-- app/ |-- about.rs -> /about |-- _marketing.rs (layout, no URL segment) |-- _marketing/ | `-- pricing.rs -> /pricing |-- posts.rs -> /posts |-- posts/ | `-- id.rs -> /posts/{post_id} `-- api/ `-- health.rs -> GET /api/health

Asset bundling The bundler scans your compiled binary for asset! calls, copies (or even downloads) every file into a local asset directory, and allows Topcoat to serve them efficiently with aggressive browser caching. const FERRIS: Asset = asset!("./ferris.png");

view! { <img src=(FERRIS)> } Topcoat also ships with utilities for web fonts and icons, as well as easy integrations for Fontsource (Google Fonts) and Iconify. Built-in Tailwind support Enabled the tailwind feature to integrate Tailwind into your project effortlessly: view! { <link rel="stylesheet" href=(topcoat::tailwind::stylesheet!())

§3 Human · 2%

> } Learn Topcoat Start here

Getting started: create a new project, install the CLI, run the dev server. Source code formatting: topcoat fmt for macro bodies.

Rendering

The view! macro: templating syntax, control flow, conditional attributes. The #[component] macro: async functions as components, with child content. The attributes! macro: reusable runtime attribute fragments. The class! macro: space-separated class lists from static and conditional entries.

Routing

Router: pages, layouts, and API routes; manual and auto-discovered. Module-based routing: derive the route table from your module tree.

Working with requests

Request context (Cx): the value pages, layouts, and components read from. App context: share long-lived values across requests, keyed by type. Memoization: #[memoize] for per-request caching and fan-out dedup. Functions, not middlewares: the recommended way to model auth and other request-scoped concerns. Cookies: read and write the request cookie jar, with signed, encrypted, and prefixed cookies. Sessions: bring-your-own-storage session authentication: login/logout lifecycle, sliding expiration, and token rotation.

Asset system

Assets: declare assets in Rust, serve them with content-hashed URLs. Fonts: bundle and serve web fonts. Icons: download Iconify icon sets or declare your own.

Third-party integrations

Tailwind: Tailwind CSS without Node, wired into the asset pipeline. htmx: drive partial HTML swaps from the server with request/response header helpers.