ESC
Type to search...
S
Soli Docs

How Soli Compares

Soli is a batteries-included MVC framework shipped as a single Rust binary — language, server, ORM, template engine, test runner, formatter, linter, LSP and deploy tooling in one executable. No Node, no bundler, no dependency install between you and a running app.

An honest tour

This page covers what Soli gives you today, where it genuinely stands out against Rails, Laravel, Phoenix and Django — and what it does not have yet. We'd rather you find the gaps here than in production.

The Philosophy

  • One binary, three commands. curl the binary, soli new, soli serve. No bundle install, no npm install, no compile step for your app code.
  • Convention over configuration, Rails-style: auto-loaded models/controllers/policies, RESTful resources routing, *_path helpers, ERB-style views.
  • Server-rendered first. htmx + Alpine.js ship vendored in every new app; instant navigation and hover prefetch are built into the server. No JS build pipeline — by design.
  • SoliDB-native. The ORM speaks to SoliDB. There are no SQL adapters — the stack is integrated top to bottom, and that trade-off is deliberate.
  • Fast by default. 170,000+ req/s on a single server: engine-per-worker architecture, production bytecode VM, in-memory asset cache.

What's in the Box

Layer What you get
Routing Verb helpers, RESTful resources (nesting, member/collection), namespaces, :param + *splat segments, named *_path/*_url helpers, per-route and scoped middleware
Controllers Class-based with inheritance, before_action/after_action (with only:/except:), per-action layouts, @ivar auto-exposure, respond_to content negotiation, halt, local-only redirect
Views ERB-style templates, auto-escaped by default, layouts + partials with locals, content_for/named yield, Rails-style form builder (form_with(post) do |f| blocks — derived URLs/verbs, value prefill, validation errors, per-form CSRF tokens, button_to, _method override, fields_for nested sub-builders), Rack-style nested params (author[name], tags[]), markdown views, i18n/date/url helpers, ammonia-backed sanitize_html
ORM Query builder, associations (belongs_to, has_many incl. through:, has_one, HABTM, polymorphic), single-collection inheritance (STI), dirty tracking, cascade deletes, counter caches, eager includes in one round-trip, grouped() read-coalescing, scopes, callbacks, validations, soft delete, encrypted attributes, transactions, state machines, native graph edges with traversal/shortest-path queries, insert-only timeseries collections with time_bucket aggregation and prune retention, grouped multi-aggregate analytics (group_by/aggregate/having), columnar stores for append-and-aggregate data, and declared-index search: vector ANN (similar), fulltext (search), geo (near/within), graph-augmented + one-call RAG (graph_rag/rag)
Realtime WebSocket rooms + presence tracking, SSE (handler-driven sse(req) and async pub/sub sse_broadcast), one-call broadcast() to WS + SSE together, LiveView (early; DOM-morphing patches) with reactive live queries (Model.live_where auto-pushes on writes), live reload in dev
Auth & security soli generate auth (Argon2id, session-fixation defense, password reset, email confirmation, remember-me, account lockout), Pundit-style policies (deny by default), attr_accessible + nested permit() mass-assignment whitelists, schema-based input validation (V, with type coercion), security headers on by default in prod, layered CSRF (Origin/Referer gate + verified per-form tokens), declarative CORS (cors("/api/*", {...}) — preflights, response headers, origin-checked CSRF opt-in), signed/encrypted cookie jar (set_cookie(..., {"signed"/"encrypted": true}) + read_cookie — HMAC-SHA256 / AES-256-GCM, name-bound, embedded expiry), SSRF-hardened HTTP client, encrypted app bundles
Jobs & mail Job.perform_later/perform_in/perform_at, cron DSL, webhook jobs with HMAC signing (queue backed by SolidB); ActionMailer-style mailer with SMTP/STARTTLS, multipart, attachments, test mode
Documents pdf_render, Factur-X / EN 16931 e-invoices (PDF/A-3b), PAdES digital signatures + timestamping, tagged PDF/UA output, merge/fill/stamp, markdown→PDF, ready-made invoice & quote templates, spreadsheets (CSV/XLSX read+write)
Tooling Parallel test runner with per-worker isolated DB + coverage gate (HTML/JSON/Cobertura), formatter, linter, static type checker (soli check), LSP + editor plugins, app-aware TUI REPL (soli in an app dir loads your models + DB — a rails console), soli routes route lister, soli graph build code-graph in SolidB for agents (graph RAG over your own source — semantic search + relationship traversal, instance-call / partial / redirect / super edges on Soli apps, soli graph query --kind / --path for agents; works on any repo — Ruby/Rails, Python, JS/TS, Rust, C# via tree-sitter), opt-in OpenAPI spec + Scalar API reference (SOLI_OPENAPI), scaffold/auth/mailer generators, engines (mountable sub-apps), soli deploy, self-executing app binaries (soli build --standalone, cross-target) with signed over-the-air auto-update (--update-url, soli update-keygen/sign-update)
Long tail S3 client, image pipeline, web push (VAPID) and a native bridge (Native.notify) that raises OS notifications inside packaged desktop/mobile shells, where web push does not exist at all, JWT, TOTP primitives, feature flags with percentage rollout, i18n, KV store (Redis-style API), SOAP client, POP3 + IMAP mail clients

Where Soli Stands Out

Performance per server

A tree-walking dev mode and a bytecode VM in production, engine-per-worker with no GIL and no process fleet. On the matched benchmark — same payload, same 16 workers, same box — a rendered HTML page measures 128,000 req/s for Soli against 11,000 for Rails and 7,200 for Django, and a database-backed page 39,000 against 8,100 and 5,500. Once a real query is in the request the multiple compresses to 4–5×, not the 10× the trivial routes show. Phoenix is the only peer here. For per-operation numbers against Ruby 4 (including YJIT and ZJIT), see Benchmarks — which is candid that Soli loses on tight interpreted loops even while winning on native transforms.

Deployment simplicity

One static binary runs your app, bundles it (soli build) into a single .soli file, or compiles app + runtime into one self-executing binary (soli build --standalone, cross-buildable for linux-amd64/linux-arm64/darwin-arm64) — optionally AES-256-GCM encrypted with RAM-only extraction for licensed/on-prem distribution, and with signed over-the-air auto-update (--update-url: P-256-signed manifests, sha256-gated, atomic self-replace) so a shipped artifact stays current. Those last capabilities have no first-party equivalent in Rails, Laravel, Phoenix or Django.

Container-native lifecycle

Liveness (/_health) and readiness (/_ready) endpoints, and a graceful drain on SIGTERM: readiness starts failing so the load balancer stops routing, new requests get a clean 503, and requests already in flight run to completion before the process exits (bounded by SOLI_SHUTDOWN_GRACE_SECS, default 25s). Rolling deploys don't truncate responses. Prometheus metrics at /_metrics round it out. Parity with the big four here, not an edge — but it means no sidecar or wrapper script is needed to deploy on Kubernetes or ECS.

Developer experience in --dev

The dev bar shows per-request queries with bind variables and durations, N+1 detection with a suggested fix, a zoomable flamegraph, per-template and per-middleware timings, and captures every XHR/htmx call a page makes — and can replay any captured request server-side to reproduce a bug. There are also preview galleries for view components (/__soli/components) and mailer templates (/__soli/mailers), and a database browser (/__soli/db) for reading your SoliDB data in-app. The dev error page includes a live REPL evaluated against the failed request's actual state. This is beyond what any of the big four ship out of the box.

Documents and compliance

European e-invoicing (Factur-X/EN 16931) down to the French mandate's party identifiers (SIREN/SIRET, BT-30/BT-47), PAdES-compliant digital signatures with timestamp authorities, PDF/A and tagged PDF/UA output, form filling, stamping and merging — built in. In every other framework this is a third-party integration project.

Frontend without a toolchain

Auto-escaped server templates, vendored htmx + Alpine, Turbo-Drive-class instant navigation and hover prefetching with ETag revalidation — zero Node, zero config.

Honest Gaps

Things a Rails, Laravel, Phoenix or Django developer will look for and not find. Some are on the roadmap; some are deliberate trade-offs.

Framework surface

  • No auto-generated admin panel — a deliberate choice in 2026: every new app ships agent-ready (CLAUDE.md/AGENTS.md, permission presets, /soli-resource), so the bet is that a coding agent plus soli generate scaffold builds you a bespoke admin faster than you'd customize a generic one. If you want a Django-admin-style CRUD UI with zero building, it isn't here.
  • View composition — layouts, partials with locals, content_for/named yield, plus a built-in component(name, data) helper (looks in components/, scaffold with soli generate component) with a block form for default + named slots (a c.slot(...) slot-builder), collection rendering, declared props with dev/lint warnings, opt-in fragment caching, a dev component catalog at /__soli/components, and paginate(pagination) for list navigation. Not a full ViewComponent system (no component classes), but a practical, growing step up from raw partials.
  • LiveView is young. Patching is DOM-aware — the client morphs nodes in place with keyed reconciliation (soli-key/soli-ignore), so focus and widget state survive updates — and it has collection streams plus reactive live queries, but the directive set is still a subset of Phoenix's: no debounce/throttle, JS commands, uploads, or nested live components.

Auth

The primitives are all here, with no third-party packages: session auth (soli generate auth, Argon2id), JWT API auth (jwt_sign/jwt_verify), native TOTP 2FA (Crypto.totp_generate/verify/uri), and OAuth consumption built on the SSRF-hardened HTTP client — with official step-by-step guides for GitHub and Google sign-in and TOTP. Signing in with another provider is still hand-rolled from those guides — there is no client generator.

Soli can also be the identity provider. soli generate oidc_provider scaffolds a working OpenID Connect provider — Authorization Code with PKCE, discovery and JWKS, consent screen, refresh-token rotation with reuse detection, and key rotation via kid. Rails, Laravel and Django all reach for a third-party package here (Doorkeeper, Passport, django-oauth-toolkit). Be equally clear about the ceiling: it implements the code+PKCE profile and nothing else — no dynamic client registration (RFC 7591), no request objects (JAR), no implicit or hybrid flows, no client-credentials or device-code grants, no front/back-channel logout, no DPoP or mTLS sender constraining, and no token introspection (RFC 7662). Native apps needing custom-scheme redirects are not supported yet either.

soli generate auth is now a one-command, Devise-style suite: login/signup plus password reset (hashed one-time tokens, 2h expiry, no account enumeration), email confirmation with resend, remember-me (HttpOnly persistent cookie, digest-stored token), and account lockout with auto-unlock — all as readable scaffolded code you own, with the thresholds as constants at the top of the generated User model. What's still assembled by hand: unlock emails and a change-email flow.

Data layer

  • SoliDB only. No PostgreSQL/MySQL/SQLite adapters, no multi-database support. If your data must live in SQL, Soli is the wrong tool today.
  • Dirty tracking is value-based (changed?, changes, previous_changes, attribute_was exist, but in-place mutation of a nested Hash/Array isn't tracked — no attribute_will_change!), through: associations can't be eager-loaded (<< pushes create the join record, but includes of a through relation raises), and cascade deletes / counter caches skip bulk writes (delete_all/update_all — matching Rails; reset_counters repairs drift).
  • Migrations roll back one step at a time — no STEP=n, no version targeting, no schema dump, no db:reset.
  • Uniqueness validation is only race-safe when backed by a database unique index — the same caveat as Rails' validates_uniqueness_of; Soli maps the index's 409 back into _errors automatically. (Conditional if:/unless: and per-operation on: create/update validation forms exist.)

Testing

The runner (parallel workers, isolated per-worker databases, coverage with a CI gate) is strong. with_transaction rolls back per-example DB writes; freeze_time/travel_to pin datetime_now(); factories support callable templates, #{n} interpolation, and Factory.insert. Request specs can also assert on the database itself — assert_no_n_plus_one, assert_query_count, assert_max_queries, or the suite-wide soli test --fail-on-n1 guard — failing on N+1s and query-budget regressions that Rails and Laravel only surface through third-party gems (Bullet, prosopite). Browser testing is built in: soli test --browser drives a real headless Chrome over the DevTools protocol — visit, click, fill_in, assert_text — with no Node, no npm and no Playwright install, and the HTTP cookie jar carries into the browser so login() still works. Responsive behaviour is testable too: a viewport("mobile") declaration in a describe body renders every test in it at an emulated device, touch and pixel ratio included — the equivalent of Capybara needing a driver-specific resize_window. Still missing: no mocking/stubbing framework, and no test filtering/watch/fail-fast flags.

Operations

  • No TLS in-process — you front Soli with a reverse proxy (HTTP/2 is cleartext-only behind it). Deliberate, but different from Phoenix.
  • Jobs require a SolidB server — there is no standalone worker daemon and no dashboard (no Sidekiq/Horizon equivalent). The opt-in in-process pool for long jobs is fire-and-forget.
  • Observability stops at metrics. Prometheus counters at /_metrics, health and readiness probes, and a rich per-request breakdown in --dev — but no distributed tracing and no structured (JSON) log output. There is no OpenTelemetry exporter, so Soli cannot yet join a trace that spans your other services, and production logs are human-formatted rather than machine-parseable. Rails, Laravel, Phoenix and Django all have mature OTel integrations.

Ecosystem

This is the honest big one: Rails has ~180k gems' worth of ecosystem, Laravel has first-party packages for billing, search and admin, Django has 15+ years of reusable apps. Soli's package registry is young. What's in the box is unusually broad — but if it's not in the box, you're writing it.

The basic package hygiene is in place, though: a project can pin a minimum interpreter version with soli_version in its soli.toml — MSRV-style, like Cargo's rust-version or Bundler's required_ruby_version — so a too-old soli fails fast with an upgrade message instead of a cryptic runtime error.

Head-to-Head

vs Ruby on Rails

Rails remains the DX benchmark, and Soli borrows its conventions deliberately.

Rails is ahead on: ActiveRecord maturity (multi-DB, richer association options, decades of edge cases), Hotwire, Devise/OmniAuth, ActiveAdmin, ActionMailbox/ActionText, system-test maturity (Capybara has a far deeper matcher and driver set than Soli's browser helpers), and the gem ecosystem.

Soli is ahead on: raw throughput and memory footprint by an order of magnitude, single-binary deployment, built-in profiling/N+1 tooling, document/e-invoice/signature capabilities, and zero-toolchain frontend.

vs Laravel

Laravel's first-party ecosystem (Horizon, Nova, Socialite, Cashier, Livewire, Octane) is the richest in the industry — Soli has no equivalents for most of it.

Soli is ahead on: performance (even against Octane), one-binary deploys with no PHP-FPM/composer/node stack, built-in realtime (rooms, presence, SSE) without Reverb or Pusher, and its testing/coverage runner.

vs Phoenix

Phoenix is the closest performance peer, and its LiveView remains ahead of Soli's — node-granular diffs, forms, uploads, nested components, and navigation. Soli's LiveView morphs the DOM in place with keyed reconciliation (widget state survives patches) and now has collection streams and reactive live queries, but its directive set is still a Phoenix subset. Elixir's OTP supervision and distribution have no Soli equivalent.

Soli is ahead on: familiarity (Ruby-like syntax vs functional Elixir + the BEAM learning curve), the document/PDF suite, integrated dev tooling, and encrypted bundle distribution. If your product is realtime UI, choose Phoenix; if realtime is a feature, Soli's WebSockets/SSE cover it.

vs Django

Django's auto-admin remains its signature feature; Soli deliberately doesn't generate one — its 2026 bet is that agent-ready scaffolding produces a bespoke admin faster than you'd bend a generic one to your needs. Django's forms + validation framework is also deeper.

Soli is ahead on: performance (vs Python), built-in realtime (Channels is a bolt-on), modern developer tooling, and deployment weight.

vs the TypeScript full-stacks (AdonisJS, NestJS, Next.js)

Soli competes here on not having a node_modules directory: comparable expressiveness, far higher throughput, and one binary instead of a toolchain. It concedes the npm ecosystem and React-style rich-client UIs.

When to Choose Soli

Good fit today

  • Server-rendered CRUD/SaaS apps with htmx-style interactivity, where throughput per server and operational simplicity are priorities
  • Document-heavy back offices: invoicing, compliance (Factur-X), signed PDFs, spreadsheet import/export
  • Products distributed on-premise or licensed, using encrypted .soli bundles or self-executing standalone binaries (no soli install on the target)
  • Teams who want Rails ergonomics without Ruby's runtime cost, and accept an integrated (SoliDB) stack

Reach for something else if you need

  • SQL, or an existing SQL database (PostgreSQL, MySQL, SQLite) — today Soli means SoliDB (itself a relational database, just not SQL)
  • A generic, zero-build admin UI (Django-style) — Soli's bet is agents + scaffolding for bespoke admins instead
  • Phoenix-LiveView-depth realtime UI, or a rich SPA toolchain
  • A large third-party package ecosystem

Bottom Line

Soli's core loop — route, controller, model, view, test, deploy — is real, fast, and safer by default than most of its inspirations. Its standout edges (performance, dev tooling, documents, single-binary ops) are genuine and hard to replicate. Its gaps (SQL, ecosystem) are equally genuine, and some absences — the generic admin, the JS toolchain — are deliberate bets rather than debt. It is best understood as a young, opinionated, vertically-integrated Rails — already excellent inside its lane, and honest about the lane's current width.