Skip to content

nimmer-jp/crown

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

42 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ‘‘ Crown Framework

The Full-stack Nim Meta-Framework. Next.js DX meets the speed of Nim.

Nim Version Basolato License


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.

โœจ Features and Philosophy

  • 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 .nim file using native procs and the html""" """ 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.

๐Ÿš€ Quick Start

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

๐Ÿ“‚ File-System Routing

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.nim or src/routes.nim. Crown generates highly-optimized routing logic automatically in the hidden .crown/ directory for you.

Crown 0.6 Router Contract

Crown 0.6 adds a route manifest and Next/TanStack-inspired routing conventions while keeping the legacy p_id syntax.

  • Dynamic segments: p_id and [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.nim files beside route folders wrap children before the central src/app/layout/layout.nim.
  • Route data loaders: proc loader*(req: Request): T can feed proc page*(req: Request, data: T): string.
  • Manifest: crown build and crown dev write .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>"""

๐Ÿ’ป 1-File Components (Showcase)

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

๐Ÿงฉ Components are just Strings

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.

Scoped CSS Components

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 label

component"""...""" (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 ?} (endif also 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.

๐Ÿ”„ State Management & Component Updates

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:

  1. Single Source of Truth (SSOT): Keep state in the parent page, UseCases, DB, or Session.
  2. Pure Functions: Components are pure props -> string functions.
  3. 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:

1. Persistent State (Parent Page Re-render)

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.

2. Partial Update Endpoints

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

3. Sibling Synchronization

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.

4. Purely Local UI State

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.

๐Ÿ“‚ Layout System

Crown provides a centralized layout system in src/app/layout/.

  • Default Layout: Any page function without a custom layout parameter is automatically wrapped by src/app/layout/layout.nim.
  • Nested layouts: Place layout.nim beside 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 page function: proc page*(req: Request, layout: Layout = "admin"). This will look for src/app/layout/admin.nim.
  • Disable layout only: htmlResponse(...).disableLayout() skips layout wrapping; injectCrownSystem still 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: post routes do not apply layouts by default. Use disableLayout() when returning full HTML documents without the central layout shell.

๐Ÿ”— Basolato and Nim versions

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.

๐Ÿ›  Command Line Interface

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 (Tiara html page, same dependencies as init). Omit dir to use the current directory, or pass a folder name to create the project inside it.
  • crown dev โ€” Boots the development server. It watches your src/ directory, auto-recompiles routes dynamically, writes .crown/dev.lock and .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 .crown directory (.crown/main) and writes .crown/manifest.json.

โš™๏ธ Compiler and Watcher Config

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.

โ˜๏ธ Production builds, Cloud Run, and containers

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 find crown.json, then loads <project>/.env and <project>/.env.local into putEnv. You can call loadCrownDotEnv() from application code if you need the same lookup again later.
  • PORT: Basolato 0.15 evaluates PORT in baseEnv at compile time when using the httpbeast server path. Always run production Docker builds with the same PORT you will inject at runtime (for example PORT=8080 crown build). crown build also passes PORT from your shell over crown.json when set. The generated main parses **runtime PORTfirst**, 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., set LOG_IS_FILE=false and LOG_IS_ERROR_FILE=false (see sample Dockerfiles in this repo).
  • HTTP helpers in crown/core: Use redirect, errorRedirect, cookies(request), setCookie(response, cookies), and queryParam instead of importing Basolato modules directly where possible.
  • Optional modules: import crown/guards (withSomeOrRedirect), import crown/rate_limit (in-memory fixed window + 429 helper), and extra Nim search paths via compilerPaths in crown.json (or nim.compilerPaths) when crown invokes nim c outside nimble's own wrapper.

๐Ÿค Contributing

We welcome contributions to make Crown the ultimate full-stack framework for Nim! Feel free to open issues or submit pull requests.

๐Ÿ“„ License

This project is licensed under the MIT License.

About

Modern HTMX and basolato based Web Framework

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages