Expand description
Topcoat
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 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
- Getting started: create a new project, install the CLI, run the dev server.
- Source code formatting:
topcoat fmtfor 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.
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 newCLI 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
-
OpenAPIendpoints - 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§
- asset
asset - Topcoat assets are declared from Rust code with
asset!. The macro returns a smallAssetID 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
Cxis Topcoat’s request context. Pages, layouts, components, and routes can take it as an optional parameter when they need request-scoped information.- cookie
cookie - Topcoat reads and writes cookies through a request-scoped cookie jar. Install cookie support on your router with
.cookies(), then callcookies(cx)from any handler to get the jar, read incoming cookies, and queue changes. Anything you add or remove during the request is serialized intoSet-Cookieresponse headers automatically once the handler returns. You don’t need to touch headers yourself. - dev
router - font
font - Web fonts are typically loaded through CSS. A set of
@font-facerules 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. - htmx
htmx - htmx is a small client-side library that lets HTML drive its own updates. Attributes like
hx-getandhx-postmake 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. - icon
icon - 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. - router
router - A
Routerhandles incoming requests. Build one withRouter::builder, register pages, layouts, layers, and API routes, callbuild, then pass it tostart. - runtime
runtime - 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. - session
session - 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.
- tailwind
tailwind - Tailwind CSS is a utility-first CSS framework: instead of writing custom stylesheets, you compose small single-purpose classes (like
flex,pt-4, ortext-center) directly in your markup, and Tailwind generates only the CSS for the classes you actually use. - view
view - This module provides Topcoat’s HTML templating primitives:
Structs§
- Error
- Error type used by Topcoat APIs.
Functions§
- serve
router - Serve a Topcoat router, notifying the topcoat dev server once the application is ready to accept connections.
- serve_
until router - Serve a Topcoat router until
signalcompletes. - start
router - Start a Topcoat router on the configured host and port.
Type Aliases§
- Result
view