Skip to main content

Crate topcoat

Crate topcoat 

Source
Expand description

Topcoat

The full full-stack framework for Rust

Crates.io Docs.rs MIT licensed Build Status Discord chat

Topcoat is a modular, batteries-included Rust framework for building fullstack apps. It prioritizes simplicity and productivity. See Learn Topcoat to get started, or the Roadmap for what’s coming next.

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.
        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

§Premade components you can edit

Topcoat UI is a component library based on Tailwind inspired by shadcn/ui. Components are copied into your project via the topcoat ui CLI command, meaning you can freely change their design and functionality to fit your use case:

#[component]
async fn delete_card() -> Result {
    view! {
        card(
            card_header(
                card_title("Delete workspace")
                card_description(
                    "This permanently removes the workspace and all of its data."
                )
            )
            card_footer(
                attrs: attributes! { class="justify-end" },
                button(variant: ButtonVariant::Ghost, "Cancel")
                button(variant: ButtonVariant::Destructive, "Delete workspace")
            )
        )
    }
}

§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!())> }

§Learn Topcoat

Start here

Rendering

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.

Client reactivity

  • The runtime: signals, $(...) expressions, @ event handlers, and : bind attributes.
  • Expressions: the dual Rust/JavaScript expression language and its vocabulary.
  • Procedures: async server functions callable from the browser.
  • Shards: components that re-render on the server when their arguments change.

UI components

  • Topcoat UI: premade components vendored into your project for you to edit.

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.

§Roadmap

Planned features we’d like to bring to Topcoat. Have an idea? Open an issue.

  • topcoat new CLI command to bootstrap pre-configured projects
  • Static export
  • (More) reactivity (topcoat-runtime)
  • More Topcoat UI components, full “blocks” e.g. sign-in form
  • Emailing
  • Better Toasty integration (safely create/update records from forms without listing out all the fields)
  • Validations
  • OpenAPI endpoints
  • Docs for how to deploy Topcoat
  • Pre-rendering for static pages
  • Streaming SSR / Suspense
  • Client-side navigation + prefetching
  • WebSockets
  • Server-sent events
  • Image optimization / resizing
  • Easier-to-use middlewares like rate-limiting, compression, etc.
  • Authentication
  • Background jobs
  • Islands

Modules§

assetasset
Topcoat assets are declared from Rust code with asset!. The macro returns a small Asset ID and embeds the declaration into the compiled binary. After building your application, Topcoat can scan the binary, copy or download every declared file into an asset bundle directory, and serve those bundled files from the router.
context
Cx is Topcoat’s request context. Pages, layouts, components, and routes can take it as an optional parameter when they need request-scoped information.
cookiecookie
Topcoat reads and writes cookies through a request-scoped cookie jar. Install cookie support on your router with .cookies(), then call cookies(cx) from any handler to get the jar, read incoming cookies, and queue changes. Anything you add or remove during the request is serialized into Set-Cookie response headers automatically once the handler returns. You don’t need to touch headers yourself.
devrouter
fontfont
Web fonts are typically loaded through CSS. A set of @font-face rules declare a font family and tells the browser where to download the font files. With Topcoat, you can programatically create these font faces and host them on your router.
htmxhtmx
htmx is a small client-side library that lets HTML drive its own updates. Attributes like hx-get and hx-post make any element issue an HTTP request on an event (a click, a submit, an input) and swap the returned HTML fragment into the page, without a full reload and without writing JavaScript. The server just answers with the markup for the piece of the page that changed.
iconicon
Icons on the web are small vector graphics that flow with the text around them: the trash can on a delete button, the magnifier in a search field. Topcoat renders icons as inline <svg> elements, so they need no extra network requests, scale with the surrounding font, and follow the text color.
routerrouter
A Router handles incoming requests. Build one with Router::builder, register pages, layouts, layers, and API routes, call build, then pass it to start.
runtimeruntime
Topcoat’s runtime makes server-rendered pages interactive without a wasm bundle, a client build step, or a separate frontend. Reactive state and expressions are written inline in view!, type-checked as ordinary Rust, and compiled to JavaScript that ships with the page.
sessionsession
Topcoat sessions implement the mechanics of session authentication – generating tokens, carrying them between client and server, and the login/logout lifecycle – while you own the storage. The framework hands you a hash and an expiry to persist in your own database, with your own ORM and schema; it never dictates a session table or a user model. The API is deliberately minimal for now and will likely be expanded over time.
tailwindtailwind
Tailwind CSS is a utility-first CSS framework: instead of writing custom stylesheets, you compose small single-purpose classes (like flex, pt-4, or text-center) directly in your markup, and Tailwind generates only the CSS for the classes you actually use.
viewview
This module provides Topcoat’s HTML templating primitives:

Structs§

Error
Error type used by Topcoat APIs.

Functions§

serverouter
Serve a Topcoat router, notifying the topcoat dev server once the application is ready to accept connections.
serve_untilrouter
Serve a Topcoat router until signal completes.
startrouter
Start a Topcoat router on the configured host and port.

Type Aliases§

Resultview