Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture choices in generated projects

cargo rullst new records product capabilities that materially change the generated application. Like 12.x, Rullst 13.0 generates one audited application architecture instead of presenting incomplete compatibility markers as equal implementations. The framework specification and capability ledger remain authoritative.

ORM style

Database-backed projects are generated with Active Record. The CLI does not ask for a global ORM architecture and does not accept an --orm flag. The framework’s repository APIs remain available when an application needs an explicit persistence boundary.

ArchitectureIn 13.0Trade-off
Active RecordThe only generated profileConcise, but persistence concerns remain close to models.
Data Mapper / RepositoryApplication-owned: the repository APIs exist, but no starter generates this profileMore explicit persistence boundary and more code to maintain.
HybridNot a global project profileThe two styles may naturally coexist per module, but the application must define that boundary.

Earlier v12 prerelease menus offered three labels, but Repository and Hybrid shared generation branches and support varied by blueprint. Removing those selectors does not remove ORM APIs; it prevents the generator from claiming equivalent end-to-end profiles before the generated routes, services and tests support them consistently. All database operations still need parameter binding, authorization and transaction design.

For most CRUD-oriented applications, start with Active Record and introduce a repository around domains that genuinely need a separate persistence boundary.

Database engines and capabilities

The first database question (--database) chooses exactly one primary backend:

SelectionBoundary
SQLiteLocal SQLx pool, migrations and relational ORM contract.
PostgreSQLSQLx PostgreSQL pool and live container CRUD/schema contract.
MySQLSQLx MySQL pool and live MySQL CRUD/schema contract.
MariaDBThe same MySQL protocol implementation with a separate live MariaDB contract.
TursoTyped Turso/libSQL primary profile for the Blank and API starters, without Nexus, Studio, Schema or the SQLx ORM; the SQLx-specific SaaS starter does not offer it.

The Blank starter can instead have no primary database (--no-database, or No database in the wizard).

A second multi-select adds zero or more independent capabilities and accepts Enter with no selection. Capabilities already selected by the primary profile or CLI flags are omitted. The same screen also offers AI, Redis and a Dockerfile (--ai, --redis, --docker); the side stores have their own flags (--turso, --mongodb, --duckdb, --surrealdb, --qdrant). Turso supplies explicit edge SQL, transactions and checked migrations; MongoDB supplies portable document CRUD; DuckDB supplies bounded analytics; SurrealDB supplies document CRUD and bounded read-only graph queries; Qdrant supplies bounded dense-vector operations. Selecting one adds the precise Cargo features and environment keys, but does not make every model portable between different database families.

This split prevents a Turso, MongoDB, analytics, or graph label from generating an application whose SQL Active Record migrations cannot run. See the Polyglot Persistence guide for the APIs and limits.

Frontend profile

Projects are generated with server-rendered html! views and HTMX enhancement (cargo rullst new reports Zero-Bundle HTMX (html! SSR)); the CLI does not ask for a frontend engine or accept a --frontend flag. The five labels shown by earlier v12 prerelease wizards were not five complete, interchangeable renderers:

Earlier labelIn 13.0
HTMX + Tailwind SSRThe generated scaffold: server-rendered html! views with the starter’s own stylesheet (static/rullst.css; no Tailwind build step). There is no project-local SPA bundle, but HTMX and styles are still browser assets that must be pinned and served.
LiveViewCore supplies LiveComponent, Live::mount and live_ws_handler, and make:live scaffolds a component. The application registers routes and supplies the HTMX WebSocket extension, authentication and reconnect policy.
Wasm Island#[island], make:island and build:client provide a dual-target hydration foundation. Asset delivery, CSP, state, routing and browser E2E remain application work.
Pico.cssNot generated. The application adds, pins and serves Pico.css and validates the resulting pages.
TeraNot generated. The application adds the dependency and its own file-template renderer.

The intended direction is to model independent capabilities instead of one misleading global frontend selector: rendering (SSR or API), interaction (HTMX, LiveView or Wasm islands), styling (the bundled design or an explicitly materialized CSS system), and templating (html! or a fully generated file-template path). A combination should appear only after its assets, routes and browser behavior have equivalent tests; 13.0 generates none of them.

Full-stack versus headless API

  • Full-stack starters (Blank, SaaS) include server-rendered routes and relevant local tool integration.
  • --api (the API starter) generates a JSON-oriented Blank project without HTML view rendering; the SaaS starter rejects it instead of silently retaining its HTML routes.

Generated code is application code. Review its routes, auth boundaries, database schema and dependencies before deployment.

Optional AI

Selecting AI adds rullst-ai and its provider-agnostic client foundation. Providers still require explicit credentials or the documented deterministic mock mode. Ollama can keep prompt traffic on infrastructure you operate, but “local” alone does not establish an air gap. Measure compile time, binary size, latency, quality and cost for the chosen provider and model.

Cache and queues

Without Redis, Rullst offers process-local memory cache and local queue options. With Redis selected, the generated project can configure the feature-gated Redis adapters.

There is deliberately no silent production fallback from Redis to memory: a distributed deployment that loses Redis must not pretend that independent process-local state is equivalent. Choose the backend explicitly and test its failure policy. Core realtime broadcast/presence remains in-process; Redis cache and queue support does not turn it into distributed WebSocket pub/sub.

Studio and Nexus

  • Studio’s supported launcher is debug-only, binds to loopback and verifies direct peer information. It shows only data supplied by real local probes.
  • Nexus uses a local-development policy, explicit release credentials or the application’s own sign-in (NexusAuthPolicy::application; see the Nexus guide). A dependency or generated route does not remove the need for application RBAC.

Neither interface should be exposed publicly without an application-owned identity, authorization, TLS and network policy.

Installable applications

Rullst uses a web-first model. The web application is the canonical, universally reachable product; the server remains authoritative for domain rules, authentication, authorization, persistence, realtime and security.

cargo rullst make:pwa makes that application installable as a Progressive Web App: a web app manifest, placeholder icons, /sw.js and an /offline page, wired into the starter’s router and layouts. The service worker works under the default nonce Content-Security-Policy and never stores a non-GET request or a response marked no-store or private, which Rullst answers by default, so authenticated pages are not kept on the device. The same site reaches Google Play as a Trusted Web Activity (a Bubblewrap configuration and /.well-known/assetlinks.json from rullst::pwa::AssetLinks) and the Microsoft Store through PWABuilder; iOS users add it from Safari. See the PWA guide.

Until 13.0 the Omni command packaged the site in Tauri shells for desktop, Android and iOS. Those shells deliberately exposed no native API to the site, so they gave the same result as an installed PWA while costing three release workflows, Tauri upgrades and per-platform signing. 13.0 removes make:omni, cargo rullst omni and the native-only offline-sync feature, whose only consumer was the shell.

rullst::client_contract supplies one shared rullst.client v1 JSON boundary for browser, server and future platform-enhanced code. It negotiates positive versions, rejects unknown outer fields and oversized bodies, and carries typed payloads, correlation, optional mutation idempotency, server time and bounded failure codes. It deliberately has no user/tenant/role claim: authentication, authorization, domain validation and durable replay handling remain server work. A page that accepts writes offline queues them in IndexedDB with an idempotency key, as the PWA guide shows; the server accepts each key once.

Engineering invariants

Repository policy requires typed failures rather than production panic!, unwrap() or expect(), explicit SQL parameterization/sanitization, and the workspace test/Clippy/format gates. These are review and CI rules, not a claim that arbitrary application code or third-party dependencies cannot panic.

Rullst exposes Axum/Tower integration so applications can add middleware and routes directly. That escape hatch also means the final security and observability composition belongs to the application.

Practical starting points

  • Single-process CRUD application: use the generated Active Record, html! SSR and HTMX profile with memory cache.
  • Domain-heavy service: introduce repositories per module with explicit transaction boundaries; 13.0 generates no Data Mapper / Repository profile.
  • Multi-process application: a configured shared cache/queue plus deployment tests; do not assume realtime is distributed.
  • Rich browser interaction: opt into LiveView or Wasm foundations only with application-owned integration and browser tests; 13.0 generates neither as a complete profile.

Revisit these choices as evidence changes. Do not infer performance, security or availability from a wizard label.