navree

Release notes

Navree 0.1.0, the first release: what each module ships, the CLI, the reference CRM and the benchmark, and the known limitations you may run into.

On this page

0.1.0 is Navree's first release. It contains 28 crates: the kernel, the shared contracts and 23 module crates, the navree facade that re-exports them, the attribute macros and the navree CLI. All of them share one version number. The repository also holds a reference CRM built on Navree and a benchmark against fourteen other framework set-ups.

Navree was developed under the working names Keel and, briefly, Navis. You may still meet those names in the repository's design notes from that time.

What's included

Foundation

  • navree-kernel: the module graph, a type-keyed dependency-injection container with singleton, request and transient scopes, lifecycle hooks, graceful shutdown and the command runner behind serve, migrate and every other command. The graph is validated before anything is constructed, and check --graph prints it.
  • navree-macros: #[module], #[injectable], #[controller], the route attributes (#[get], #[post], #[put], #[patch], #[delete]) and the helper attributes for access, throttling, timeouts, transactions, audit actions and OpenAPI metadata. A route without an access declaration doesn't compile.
  • navree-contracts: the small shared traits and types that let modules cooperate without depending on each other: Secret<T>, RequestId, Principal, jobs and dispatchers, sinks for audit and events, Broadcaster and Clock.
  • navree-config: typed configuration from .env, TOML files and APP_* environment variables, validated at boot, with secrets redacted in logs and errors.
  • navree-log: a tracing subscriber with pretty or JSON output, field redaction, per-target levels and rotating log files.

HTTP

  • navree-http: Axum integration with a default pipeline (request ids, client IP resolution through trusted proxies, panic catching, timeouts, security headers, compression, a per-IP rate limit, body limits, maintenance mode), controllers, deny-by-default route access, per-route throttles, signed URLs, CORS and errors in the NestJS format ({statusCode, message, error}, validation failures as 400 with errors).
  • navree-validation: Valid<Json<T>>, Valid<Query<T>> and friends on garde, with class-validator-style messages and async unique and exists rules.
  • navree-api: an OpenAPI 3.1 document built from route metadata at /api/openapi.json, a docs UI at /api/docs, openapi:export, and API resource helpers.
  • navree-session: encrypted session cookies with memory and database stores, and CSRF protection. It is optional; the CRM and new apps don't use it.

Data

  • navree-db: SeaORM integration with a unit of work per request (a transaction that commits when the response succeeds), Repo with change events, per-module migration sets, seeders, model factories, pagination and the migrate, migrate:rollback, migrate:fresh, migrate:status and db:seed commands. SQLite by default; PostgreSQL with the postgres feature.
  • navree-cache: a memory cache store on moka with remember, tags, stale-while-revalidate and atomic locks.

Security

  • navree-auth: NestJS-style authentication with 15-minute JWT access tokens and 30-day rotating refresh tokens with reuse detection, Passport-style strategies, CurrentUser, registration, password change and reset, email verification, TOTP two-factor authentication with recovery codes, and login throttling.
  • navree-access: roles, permissions and CASL-compatible abilities, record checks for #[can(.., from = "id")] routes, SQL list filters with Accessible, the abilities endpoint for front ends, management routes and the access:role, access:grant and access:explain commands.
  • navree-crypto: AES-256-GCM encryption with key rotation, Argon2id password hashing on a bounded blocking pool, token helpers and key:generate.
  • navree-audit: an audit trail of data changes, sign-ins, permission changes and access denials, with audit:prune.

Background work

  • navree-queue: database and memory job queues with workers, retries with backoff, unique and delayed jobs, failed-job storage and the queue:work, queue:failed, queue:retry and queue:flush commands. Jobs dispatched during a request are queued only if its transaction commits.
  • navree-events: a typed event bus with synchronous and queued listeners.
  • navree-schedule: cron-style schedules in UTC, overlap prevention, single-node runs across servers sharing a database, and schedule:run, schedule:work and schedule:list.

Communication

  • navree-mail: mailables rendered from templates, with SMTP, log, array and failover transports, and queued sending.
  • navree-notify: notifications over mail, the database and broadcasts, with read state.
  • navree-realtime: an in-process WebSocket server speaking the Pusher protocol, so Laravel Echo and pusher-js work unchanged, with public, private and presence channels and a signed relay that lets workers broadcast through the serving process.
  • navree-view: MiniJinja templates for mail and other text.

Integrations and operations

  • navree-storage: named disks on object_store (local filesystem and memory), streaming, signed temporary URLs and upload checks.
  • navree-http-client: outgoing HTTP with retries, timeouts, an SSRF policy and fakes for tests.
  • navree-observe: /health/live, /health/ready with per-module checks, and Prometheus metrics at /metrics.

Testing

  • navree-testing: TestApp to boot an app or a few modules on a temporary database, an in-process TestClient with cookies, acting_as, JSON assertions and validation-error assertions, and fakes.

The CLI

The navree binary (crate navree-cli) creates apps, generates code and runs the dev loop:

  • navree new <name>, with --ui next for a Next.js front end that keeps tokens in httpOnly cookies, and --navree-path to choose the Navree checkout.
  • navree make:module <name> [--crud] and twelve make:<kind> generators: controller, service, entity, migration, job, event, listener, mail, notification, policy, command and test. Generators never overwrite files; they register what they create at marker comments.
  • navree dev, which rebuilds and restarts the app when its code changes, and keeps the last good build serving when a build fails.
  • Every other command is forwarded to the app's binary.

The reference CRM

apps/crm-api is a CRM API built on Navree: companies, contacts, a deals pipeline, activities, a dashboard, users and roles, with live updates over WebSockets, queued notifications, scheduled reminders and an audit log. apps/crm-web is its Next.js front end, whose server holds the tokens for the browser. Unit, feature and Playwright end-to-end tests cover both. The CRM tour walks through it.

The benchmark

bench/ compares Navree with Loco, Axum, Actix Web, Gin, ASP.NET Core, Spring Boot, NestJS (Express and Fastify), Express, FastAPI, Django, Laravel (Octane and PHP-FPM) and Rails, each implementing the same API and passing the same conformance checks. Every implementation runs against PostgreSQL, Navree's included: its benchmark app uses navree-db's pool, the unit of work, pagination, Repo::insert and navree-auth's user lookup on a real PostgreSQL server, with no changes to the framework. The benchmarks page has the results.

Design decisions

A few things work differently from what the original plan described:

  • Authentication and errors follow NestJS, not Laravel Sanctum. Navree issues stateless JWT access tokens and rotating refresh tokens; navree-auth has no SPA cookie authentication, no personal access tokens and no session login. Errors use NestJS's {statusCode, message, error} format; there are no application/problem+json responses and no 422 or 419 statuses.
  • Real time is built in. Instead of relying on an external WebSocket server, Navree ships an in-process Pusher-protocol server. Processes that don't serve the sockets send their broadcasts to the one that does over a signed HTTP relay.
  • The framework's own crates don't use the macros. They implement Module, Injectable and Controller by hand, the escape hatch every app can use too. Apps use the macros.

Known limitations

These are the gaps in 0.1.0 you may run into. Most are listed again, as future work, on the roadmap.

Installation and tooling

  • The crates aren't on crates.io. Without a local checkout, navree new writes navree = "0.1.0", which doesn't resolve yet.
  • navree help prints the CLI's own help, not your app's commands, although the generated README says otherwise. Use navree list.
  • navree dev doesn't keep the listening socket open across restarts, so requests fail while the server restarts. If navree dev is killed forcibly, the server it started keeps running.

Configuration

  • In ConfigSources::defaults(), config/app.toml overrides .env, not the other way round. A key set in both keeps the TOML value, so editing APP_HTTP__ADDR or APP_DATABASE__URL in a new app's .env has no effect. Real environment variables do override the file. The comments in the generated .env.example and config/app.toml describe this the wrong way round. See Configuration.
  • ConfigSources::defaults() reads the real APP_* environment variables, so tests that use it can be affected by the machine they run on. Build test configuration with ConfigSources::new().

Database

  • PostgreSQL support compiles and runs in the benchmark, but the test suite runs on SQLite only. Some PostgreSQL paths haven't been run against a real server yet, among them migrations, the FOR UPDATE row locks in Repo::update, Repo::find_for_update and record checks, and the queue's FOR UPDATE SKIP LOCKED job reservation; the database page has the full list.
  • navree-db leaves sqlx's test_before_acquire on, so each connection checkout costs an extra round trip, and there is no setting to turn it off.

Authentication and security

  • A request with a present but invalid JWT gets a 401 on every route, public ones included. Clients must not send stale access tokens to /api/auth/login, /api/auth/refresh or the health probes.
  • Access tokens are stateless: they stay valid until they expire (15 minutes by default), even after logout or a password change.
  • The refresh_tokens table grows with every refresh and there is no command to prune it yet. A refresh token's lifetime slides: each rotation gets a fresh 30 days.
  • Registration, forgot-password and reset-password have no route-specific rate limits: only the global per-IP limit and the per-address mail throttle apply.
  • POST /api/auth/2fa/enable and POST /api/auth/2fa/recovery-codes need only a valid access token, not the password.
  • A bad or expired signed URL answers 401, where Laravel answers 403.

Validation and access

  • serde errors (a wrong type, a missing field, an unknown enum value) are reported one at a time, before and instead of the garde rules. A body with an empty name and an invalid size reports only size.
  • The record loader behind #[can(.., from = "id")] has no soft-delete scope, so a user whose rules deny the action gets 403 rather than 404 on a soft-deleted record. Async exists rules can't add deleted_at IS NULL either; check deleted records in your service.
  • A record check that fails answers 403 even when the user may not read the record, which tells the caller the id exists.
  • There is no command to assign a role to a user; use the access management routes or a seeder.

OpenAPI

  • The document can't describe query parameters or non-JSON bodies, such as a CSV export.
  • navree-db's Page<T> has no OpenAPI schema, so a paginated route documents its response with a page struct of its own, as the CRM's DealPage does (#[api(response = DealPage)]).
  • The user in login responses and GET /api/auth/me is documented without the fields an app adds to it.
  • Framework routes without schemas: the abilities and access management routes, broadcasting auth, the WebSocket endpoint, the health probes and /metrics.
  • Every operation lists a cookie security scheme, even in apps that use bearer tokens only.
  • The docs UI loads its script from jsDelivr without a subresource integrity hash.

HTTP and other modules

  • navree-http doesn't map multipart errors itself: only apps with StorageModule answer malformed uploads with a 4xx instead of a 500.
  • navree-http-client can't pick up the current request's id on its own; pass it with .with_request_id(&id).
  • The memory cache store has no size limit, and there are no Redis drivers for the cache, the queue, sessions, locks or real-time scaling.
  • Storage disks are the local filesystem and memory only.
  • navree-session still serves GET /sanctum/csrf-cookie, a leftover from the original Sanctum plan. The module is optional.

Kernel and testing

  • A singleton that stores the AppContext creates a reference cycle, so that app's memory is never freed. It doesn't matter for a long-running process, but leaks in tests that boot many apps; resolve lazily with r.lazy::<T>() instead.
  • A provider registered with .lifecycle::<T>() must be a singleton. A request-scoped one fails at boot, and check doesn't catch it.
  • On Windows, a TestApp dropped without app.shutdown().await may leave its SQLite files in the temporary directory, because the pool is still open.
  • TestAppBuilder::fake_auth replaces the app's authenticator completely, so one test app can't mix acting_as with real bearer tokens.

Performance

Navree's own per-request cost is high for a Rust framework: on plain JSON it delivers 22% of a hand-written Axum service's throughput. The benchmark's findings trace this to specific parts of the pipeline, listed on the roadmap.

Deliberately deferred

Redis drivers, apalis as a queue backend, passkeys, social login, an OAuth2 server, billing, AI, gRPC, search and other modules were left out of 0.1 on purpose. The roadmap lists them with the reasons.

Popular pages

  • Installation
  • Your first app
  • Routing and controllers
  • Validation
  • Authentication
  • Benchmarks