Crown is a next-generation meta-framework for the Nim programming language. It combines the blazing-fast performance of Nim with a modern, file-system-based routing architecture inspired by Next.js (App Router), empowering you to build hypermedia-driven applications with zero client-side JavaScript configuration.
- Next.js-like Developer Experience (DX): Ditch manual route definitions. Crown uses a File-System Routing architecture where your
src/app/directory perfectly mirrors your app's URLs. - Zero JS (State Management Free): Crown treats HTML as the engine of application state. By leveraging HTMX automatically under the hood, you get SPA-like partial page updates and rich UX without writing a single line of client-side JavaScript.
- Solid Bedrock: We didn't reinvent the wheel. Crown is built upon Basolato, leveraging its battle-tested HTTP server, security, and session management capabilitiesโcompletely abstracted away for your convenience.
- Component Co-location: No templating black magic. Write your views and your backend logic cleanly together in the same
.nimfile using native procs and thehtml""" """macro. - Tailwind CSS Ready: Crown automatically injects Tailwind CSS by default, enabling you to style your application instantly without complex build pipelines.
- Zero-Config PWA: Automatically generates a Service Worker, manifest, and offline fallback (including Background Sync for API requests) with a single flip of a flag in
crown.json.
Initialize a new Crown project in seconds:
# 1. Install the crown CLI (using Nimble)
nimble install -y https://github.com/valit/crown
# 2. Create a new directory and initialize the project
mkdir my_awesome_app
cd my_awesome_app
crown init
# 3. Start the development server
crown dev(Your app will be available at http://localhost:8080/ by default; hot-reloading is enabled in development.)
Say goodbye to routes.nim boilerplate. With Crown, your directory structure defines your routes.
my_awesome_app/
โโโ crown.json # Configuration
โโโ src/
โ โโโ app/
โ โโโ layout/
โ โ โโโ layout.nim # Default layout (wraps pages by default)
โ โ โโโ admin.nim # Custom "admin" layout
โ โโโ page.nim # Automatically maps to GET `/`
โ โโโ editor/
โ โ โโโ page.nim # Automatically maps to GET `/editor`
โ โโโ (admin)/
โ โ โโโ users/
โ โ โโโ [id:int]/
โ โ โโโ page.nim # Maps to GET `/users/{id:int}`
โ โโโ api/
โ โโโ save.nim # Automatically maps to POST/GET `/api/save`
โโโ public/ # Static assets
Warning: Never manually create
src/main.nimorsrc/routes.nim. Crown generates highly-optimized routing logic automatically in the hidden.crown/directory for you.
Crown 0.6 adds a route manifest and Next/TanStack-inspired routing conventions while keeping the legacy p_id syntax.
- Dynamic segments:
p_idand[id]map to{id:str}. Use[id:int]for numeric Basolato params. - Route groups: folders like
(admin)are ignored in the URL but remain available in.crown/manifest.json. - Nested layouts:
layout.nimfiles beside route folders wrap children before the centralsrc/app/layout/layout.nim. - Route data loaders:
proc loader*(req: Request): Tcan feedproc page*(req: Request, data: T): string. - Manifest:
crown buildandcrown devwrite.crown/manifest.json; dev also serves it at/__crown/manifest.
import crown/core
type UserVm* = object
id*: int
proc loader*(req: Request): UserVm =
UserVm(id: req.param("id", int))
proc page*(req: Request, data: UserVm): string =
html"""<h1>User {data.id}</h1>"""Typed request helpers are available for route params, query params, and form values:
type SearchInput = object
q*: string
page*: int
proc page*(req: Request): string =
let input = req.search(SearchInput)
let id = req.param("id", int)
html"""<p>{input.q} / {input.page} / {id}</p>"""Crown allows you to elegantly handle multiple HTTP methods inside of a single page module. Return partial HTML snippets to handle hx-post requests magically.
# src/app/editor/page.nim
import crown/core
import tiara/components # Assuming you use Tiara UI components
# 1. Handle POST requests (Partial Updates)
proc post*(req: Request): string =
let content = req.postParams.getOrDefault("content", "")
# ... Perform DB Save Operations ...
# Return a partial HTML snippet
return html"""
<div id="save-status" class="tiara-toast-success">
Successfully saved!
</div>
"""
# 2. Handle GET requests (Full Page Renders)
proc page*(req: Request): string =
let initialContent = "Start typing here..."
# By default, the output is injected into `src/app/layout/layout.nim`
return html"""
<div class="tiara-container">
...
</div>
"""
# 3. Handle GET requests with Custom Layout
proc page*(req: Request, layout: Layout = "admin"): string =
# Explicitly using "admin" maps to `src/app/layout/admin.nim`
return html"""
<div>Admin Dashboard</div>
"""In Crown, HTML is primarily written with html""" and returned as a string.
Reusable UI pieces are ordinary Nim procedures that also return string.
proc badge(text: string): string =
component"""<span class="badge">{text}</span>"""
proc page*(req: Request): string =
html"""
<div>
{badge("new")}
</div>
"""(Note: component is provided as an optional alias for html to improve code readability.)
If you are migrating from pure Basolato and prefer using Templi Component objects, Crown does natively support returning Component and Future[Component] from routes as a backwards-compatibility feature. Components from Basolato are re-exported in crown/core.
You can define components with co-located scoped CSS using the component name(args): macro form.
Inside css:, .self is replaced with a compile-time generated unique class.
Inside html:, class="self" (or "foo self bar") is replaced with the same class.
import crown/core
component myButton(label: string):
css: """
.self {
padding: var(--space-4);
background: var(--primary);
}
.self:hover { opacity: 0.8; }
"""
html:
button(class="self"):
text labelcomponent"""...""" (string alias) is still available for backwards compatibility.
You can also write raw HTML directly in html::
component myButtonRaw(label: string):
css: """
.self { padding: 8px 12px; }
"""
html: """
<button class="btn self">{label}</button>
"""Raw HTML mode also supports PHP-like directives:
component listPanel(items: seq[string], showHeader: bool):
css: ".self { padding: 12px; }"
html: """
<section class="self">
{? if showHeader ?}
<h2>Items</h2>
{? end ?}
<ul>
{? for item in items ?}
<li>{?= item ?}</li>
{? endfor ?}
</ul>
</section>
"""Supported directives:
{? if ... ?},{? elif ... ?},{? else ?},{? end ?}(endifalso allowed){? for ... ?},{? while ... ?},{? case ... ?},{? of ... ?},{? endfor ?}{?= expr ?}(inline expression output)
Inside html:, control flow (if / for / case) and nested function/component calls are supported.
Void elements like input and br are emitted without closing tags.
Crown explicitly avoids React-like two-way data binding or client-side component state synchronization. Instead, it fully embraces a Server-Driven Re-rendering model.
The core philosophy is simple:
- Single Source of Truth (SSOT): Keep state in the parent page, UseCases, DB, or Session.
- Pure Functions: Components are pure
props -> stringfunctions. - Re-render on Change: When a child needs an update, recalculate and return the new markup.
Here are the 4 practical patterns for handling state in Crown:
When updating DB, Session, or Form values, update the state on the server and re-render the target components (or the entire parent page). This is the standard, simplest approach.
If you want to update only a specific component from the parent page, do not mutate it directly. Instead, create a dedicated endpoint (e.g., src/app/sidebar/page.nim returning sidebar(vm)) and fetch its markup partially via HTMX (hx-get).
When updating one component affects another (e.g., an editor save updates a sidebar document count), rebuild the shared state on the server and return both pieces of HTML. You can swap them simultaneously using HTMX's Out-of-Band (OOB) swaps or simply re-render the parent target containing both.
For transient states like modal visibility, active tabs, or drag-and-drop indicators, use lightweight client-side JavaScript (Vanilla JS, Alpine.js, etc.). Do not try to sync these micro-states with the server.
Crown provides a centralized layout system in src/app/layout/.
- Default Layout: Any
pagefunction without a custom layout parameter is automatically wrapped bysrc/app/layout/layout.nim. - Nested layouts: Place
layout.nimbeside route folders (see Crown 0.6 Router Contract). They wrap children before the central layout. - Custom Layouts: You can specify a layout by adding a second parameter to your
pagefunction:proc page*(req: Request, layout: Layout = "admin"). This will look forsrc/app/layout/admin.nim. - Disable layout only:
htmlResponse(...).disableLayout()skips layout wrapping;injectCrownSystemstill runs (Tailwind, client JS, dev overlay). - Disable Crown inject only:
disableCrownInject()skips Tailwind / client scripts / PWA tags. Layout behavior is unchanged. - Legacy โskip everythingโ:
disableLayout(keepInject = false)disables both layout and inject (older Crown behavior). - Partial HTML:
postroutes do not apply layouts by default. UsedisableLayout()when returning full HTML documents without the central layout shell.
Crown supports Basolato 0.15.0 through 0.16.x and Nim 2.2.x (crown.nimble requires Nim โฅ 2.2.0). For the current stable patch line, Nim 2.2.10 or newer is recommended (release notes).
| Basolato | Controller shape | Notes |
|---|---|---|
| 0.16.x | proc(c: Context): Future[Response] with let p = c.params() |
Generated .crown/routes.nim uses crownRouteRegister("get", ...) and Routes.merge when available. |
| 0.15.x | proc(c: Context, p: Params): Future[Response] |
Same macro; when compiles picks the two-argument branch. Routes.merge is omitted; routes are a seq[Routes]. |
HTTP backend: Basolato 0.16 with the stdlib server can hit GC-safety / createResponse issues on Nim 2.2; crown build and crown dev default to -d:httpbeast. For Basolato installs that need SSL and local app imports, Crown also adds -d:ssl, --path:., and --nimcache:./nimcache unless you explicitly override them in crown.json.
Name clashes: In generated routes, Crown uses import crown/core as crown and qualifies crown.Request / crown.Response so they do not collide with Basolatoโs request / response modules. The crownRouteRegister template uses exported aliases BasolatoContext, BasolatoParams, and BasolatoHttpResponse internally so expansion stays unambiguous with import basolato/controller.
Issue repro (Basolato 0.15 + Nim 2.2): Pin Basolato with nimble install https://github.com/itsumura-h/nim-basolato#v0.15.0 (the Git tag is v0.15.0, not 0.15.0), use Nim 2.2.x from PATH (not a broken Nimble-cached copy), set SECRET_KEY before Basolato loads (see tests/crown_test_env.nim), add -d:httpbeast as in tests/config.nims, then nimble test from the Crown repo or nim c -r -d:httpbeast tests/tcrown_route_register.nim.
Crown comes with a powerful CLI to manage your project lifecycle.
crown initโ Scaffolds a new barebones project structure natively.crown create-app [dir]โ Scaffolds a Crown + Tiara starter (Tiarahtmlpage, same dependencies asinit). Omitdirto use the current directory, or pass a folder name to create the project inside it.crown devโ Boots the development server. It watches yoursrc/directory, auto-recompiles routes dynamically, writes.crown/dev.lockand.crown/dev.log, and forwards browser errors to.crown/browser.log.crown buildโ Compiles your application into a highly optimized, production-ready static binary inside the.crowndirectory (.crown/main) and writes.crown/manifest.json.
You can extend the Nim compiler flags used by both crown build and crown dev in crown.json.
{
"port": 8080,
"tailwind": true,
"pwa": false,
"compilerPaths": ["./vendor/my-nim-pkg"],
"nimFlags": ["-d:ssl"],
"buildFlags": ["-d:release", "-d:production_db"],
"devFlags": ["--hints:off", "-d:dev_db"],
"watchDirs": ["config"],
"watchFiles": ["app.env"]
}nim.flags, nim.buildFlags, nim.devFlags, watch.dirs, and watch.files are also accepted as nested forms. The dev server now inherits the full parent environment and overrides only PORT and ENV, so secrets like TURSO_DATABASE_URL and TURSO_AUTH_TOKEN continue to be available in the child process.
By default, Crown now compiles with -d:httpbeast (Basolato's httpbeast backend), -d:ssl, --path:., and --nimcache:./nimcache.
If you want to switch backend or disable SSL explicitly, set define/undef flags in nimFlags, buildFlags, or devFlags (for example "-u:httpbeast", "-d:httpx" or "-u:ssl"). A custom --nimcache:... flag disables Crown's default nimcache path.
Basolato reads several environment variables very early (see upstream baseEnv.nim). Crown adds the following conveniences:
crown/dotenv+.crown/crown_env_preserver.nim: Before Basolato is imported, Crown walks upward from the process working directory to findcrown.json, then loads<project>/.envand<project>/.env.localintoputEnv. You can callloadCrownDotEnv()from application code if you need the same lookup again later.PORT: Basolato 0.15 evaluatesPORTinbaseEnvat compile time when using thehttpbeastserver path. Always run production Docker builds with the samePORTyou will inject at runtime (for examplePORT=8080 crown build).crown buildalso passesPORTfrom your shell overcrown.jsonwhen set. The generatedmainparses **runtimePORTfirst**, then falls back to the compile-time snapshot and finallycrown.json`.- Logging: Basolato defaults to file logging under
LOG_DIR(./logs). On read-only or non-root images that can raise permission errors. For Cloud Run, Fly.io, etc., setLOG_IS_FILE=falseandLOG_IS_ERROR_FILE=false(see sample Dockerfiles in this repo). - HTTP helpers in
crown/core: Useredirect,errorRedirect,cookies(request),setCookie(response, cookies), andqueryParaminstead of importing Basolato modules directly where possible. - Optional modules:
import crown/guards(withSomeOrRedirect),import crown/rate_limit(in-memory fixed window +429helper), and extra Nim search paths viacompilerPathsincrown.json(ornim.compilerPaths) whencrowninvokesnim coutsidenimble's own wrapper.
We welcome contributions to make Crown the ultimate full-stack framework for Nim! Feel free to open issues or submit pull requests.
This project is licensed under the MIT License.