Expand description
The full full-stack framework for Rust
Topcoat is a modular, batteries-included Rust framework for building full-stack 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::{module_router, page},
view::{View, component, view},
};
#[tokio::main]
async fn main() {
topcoat::start(module_router!().build()).await.unwrap();
}
#[page]
async fn home() -> Result<impl View> {
Ok(view! {
<!DOCTYPE html>
<html>
<body>
hello(name: "World")
</body>
</html>
})
}
#[component]
async fn hello(name: &str) -> Result<impl View> {
Ok(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:
#[component]
async fn faq(cx: &Cx) -> Result<impl View> {
let open = signal(cx, || false);
Ok(view! {
// 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 full-stack 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(cx: &Cx) -> Result<impl View> {
let query = signal(cx, String::new);
Ok(view! {
<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<impl View> {
Ok(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<impl View> {
Ok(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
Enable the tailwind feature to integrate Tailwind into your project:
view! { <link rel="stylesheet" href=(topcoat::tailwind::stylesheet!())> }§Learn Topcoat
Start here
- Getting started: create an app with
topcoat newand run the dev server. - Manual setup: add dependencies and wire up a minimal app yourself.
- Source code formatting:
topcoat fmtfor macro bodies.
Rendering
view!mixes HTML, Rust expressions, control flow, and component calls.#[component]makes async functions callable from markup with named arguments and child content.attributes!collects attributes to pass through components and spread onto elements.class!joins base, optional, and conditional CSS classes.suspenseshows a fallback while content loads.error_boundaryshows one if rendering fails.live!andemit!replace part of a page as async work produces new HTML.
Routing
- Router: pages, layouts, and API routes; manual and auto-discovered.
- Module-based routing: derive the route table from your module tree.
- Linking to handlers: use
href!for navigation links, form actions, and redirects.
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.
- Client-side navigation: links, prefetching, and navigation without a full document reload.
Miscellaneous
- Topcoat UI: premade components vendored into your project for you to edit.
- Localization: choose the language for each request and display translations from Fluent files.
- Mail: declare mail with the
mail!macro, deliver through SMTP, file, or in-memory transports.
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.
- Alpine AJAX: drive partial HTML swaps from the server with Alpine AJAX’s request-header conventions.
- Datastar: patch elements and signals into the page over server-sent events.
§Roadmap
Planned features we’d like to bring to Topcoat. Have an idea? Open an issue.
- Static export
-
(More) reactivity (
topcoat-runtime) - More Topcoat UI components, full “blocks” e.g. sign-in form
- Better Toasty integration (safely create/update records from forms without listing out all the fields)
- Validations
-
OpenAPIendpoints - Docs for how to deploy Topcoat
- Pre-rendering for static pages
-
WebTransport - Image optimization / resizing
- Markdown support
- Easier-to-use middlewares like rate-limiting
- Authentication
- Background jobs
- Islands
Modules§
- alpine_
ajax alpine-ajax - Alpine AJAX updates parts of a page with HTML from the server. Add
x-targetto a form or link to select which elements the response replaces. - asset
asset - Declare static files with
asset!and use the returnedAssetas a URL in your views. Topcoat bundles the files after building your application. You can serve the bundle through the router or upload it to an external host. - context
Cxgives handlers and components access to request data and shared application values.- cookie
cookie - Topcoat reads and writes cookies through a jar shared by the request. Register
.cookies()on the router, then callcookies(cx)to read cookies or queue changes. When the handler returns, Topcoat adds the changes to the response asSet-Cookieheaders. This also works when the handler returns an error or redirect. - core
- datastar
datastar - Datastar updates HTML and reactive signals from server responses. Use
data-*attributes to bind signals to elements and actions such as@getto send requests. - dev
router - font
font - Declare web fonts in Rust and serve their CSS through the router. Each
@font-facerule tells the browser which font file to load and when to use it. - htmx
htmx - htmx updates parts of a page with HTML from the server. Attributes such as
hx-getsend requests and select where the response appears. - icon
icon - Topcoat renders icons as inline
<svg>elements. Icons scale with the surrounding text and need no separate network request. - l10n
l10n - Use localization to show text in each user’s language, whether it ends up in a page, an email or a push notification. Enable the
l10nfeature oftopcoat, choose a locale, and store it in the context. - mail
mail - Declare email with
mail!and deliver it withsend. ATransportdetermines how messages are delivered. - router
router - A
Routermatches requests to handlers. You can derive paths from Rust modules or write them directly on handlers. Module routing is the recommended default. Both approaches build the same router, which you pass tostartto serve requests. - runtime
runtime - Topcoat’s runtime adds browser interactions to server-rendered pages. Write state and expressions in Rust alongside your markup. The runtime compiles the browser code to JavaScript and includes it with the page.
- session
session - Topcoat manages session tokens. Your application stores each token’s hash and expiry, associates it with a user, and checks that record on later requests. You choose the database and schema.
- tailwind
tailwind - Tailwind CSS generates CSS from the utility classes in your markup, such as
flexandtext-center. - view
view - Build HTML with
view!and define reusable components with#[component]. Theview!guide introduces markup, Rust expressions, and rendering.
Structs§
- Error
- Error type used by Topcoat APIs.
Functions§
- serve
serve - Serves a router and notifies the dev server when it is ready.
- serve_
until serve - Serves a router until
signalcompletes. - start
serve - Starts a router on the configured host and port.