navree
Documentation

Starter kits

Production-ready apps from one command: a SaaS kit with a Next.js and shadcn/ui front end, an API kit and a microservice kit.

On this page

Introduction

A starter kit is a complete application, written into your directory by navree new, that you own from the first commit. Instead of an empty app with one example module, you start with the parts every product needs already built, tested and wired together: sign-in, workspaces, billing, settings, an admin panel, background work, local services and a deploy configuration.

Pick the kit for the shape you are building:

KitCommandWhat you get
saasnavree new acme --kit saasA Navree API and a Next.js and shadcn/ui front end: sign-in, two-factor, workspaces with members and roles, billing, settings and an admin panel, behind a backend-for-frontend
apinavree new acme --kit apiThe same API without the front end: accounts, workspaces, billing and OpenAPI with a committed contract, for mobile apps, partners or a front end you build yourself
micronavree new billing --kit microA microservice: a message handler and an event handler on NATS, health checks and metrics, and a contracts crate for its clients

Kits are generated code, not a framework layer. There is no kit runtime and nothing to stay compatible with: every route, screen and test is a file in your repository, written with the same modules, macros and conventions as the rest of these docs.

Requirements

  • Rust (the stable toolchain) and the Navree CLI.
  • Docker or Podman, for the local services that Dock starts: TimescaleDB and Mailpit for saas and api, TimescaleDB and NATS for micro.
  • For the saas kit, Node.js 22 or newer and pnpm.

Creating an app

Shell
Text
  1. Start the local services

    navree dock up reads dock.toml and starts TimescaleDB (PostgreSQL with hypertables, Navree's default database) and Mailpit in containers. navree new already wrote their settings into .env, between the # dock:start and # dock:end markers: APP_DATABASE__URL points at the database and APP_MAIL__TRANSPORT at Mailpit's SMTP port. Mail the app sends shows up at http://localhost:8025.

  2. Set the application key

    navree key:generate prints a key; put it in .env as APP_KEY. It encrypts cookies and secrets, and signs tokens, so keep it out of git (.gitignore already lists .env).

  3. Create the tables and the demo data

    navree migrate:fresh --seed runs every module's migrations and seeds one workspace, demo, with an owner ([email protected], also a super admin for /admin) and a member ([email protected]), both with the password password123, and three example projects.

  4. Run it

    pnpm --dir web install installs the web app's packages. navree dev serves the API, rebuilding it when the Rust code changes, and starts the web app's dev server (pnpm run dev in web/; --no-web skips it). Open http://localhost:3000 and sign in as the owner.

navree new takes the same options with every kit: --navree-path <path> depends on a local Navree checkout, and --database postgres runs plain PostgreSQL instead of TimescaleDB (timescaledb = false in dock.toml; the kits run on PostgreSQL, so mysql and sqlite are for the minimal app). A kit brings its own front end or none: --kit saas --ui next is the same as --kit saas, and --ui next with api or micro is an error. The name may use lower-case letters, digits, - and _, and the target directory must be empty or missing. When rustfmt is installed, the generated Rust is formatted with it.

The SaaS kit

The SaaS kit is a working multi-tenant product: people sign up, create a workspace, invite their team, pick a plan and manage everything from settings, while you manage the whole system from an admin panel.

What users get

AreaScreens and flows
Sign-inEmail and password, two-factor with an authenticator app and recovery codes, social sign-in buttons for every provider you configure
AccountsSign-up with email verification, forgotten password, password reset
WorkspacesCreate a workspace (onboarding), switch between workspaces, workspace URLs under /w/<slug>/, accept an invitation
MembersInvite by email with a role, change a member's role (Admin, Member, Viewer), remove members, cancel invitations, transfer ownership
BillingPlans with monthly and yearly prices, trials, checkout, the customer portal, invoices; seats follow the member count
SettingsProfile, password, two-factor, active sessions, workspace name and slug, notifications
ProductA dashboard and an example feature, projects, with a list, a status filter, search, pagination, a detail page, real-time updates and access rules per role
AdminThe Helm admin panel at /admin for super admins: users, workspaces and subscriptions

How it is built

The API is a modular monolith. Its root adds the framework modules, configured once, then the kit's feature modules:

src/app.rs

platform in src/platform.rs lists the framework modules the kit uses: Config, Logger, Crypto, Http, Database, Cache, Events, View, Mail, Queue, Schedule, Validation, Auth, Access, Workspace, Billing, Social, Notify, Realtime, Audit, Api, Observe and Helm, plus the authorizer of the private channel private-workspace.{id}, which only the workspace's members may subscribe to.

Every product route is scoped to a workspace. The example feature shows the pattern the rest of your features follow:

src/modules/projects/controller.rs

The InWorkspace guard resolves the workspace from the request and checks that the user is a member before the access rules run, so #[can] and Accessible decide with the user's role in that workspace. The kit uses row isolation: the service reads and writes through WorkspaceRepo, so every query on a workspace table is filtered by workspace_id, and its migrations add PostgreSQL row-level security policies. The module registers m.workspace_scoped::<Project>(), so navree check verifies all of this. See Multi-tenant SaaS and Workspaces.

The front end is a Next.js app in web/ with shadcn/ui components, TanStack Query and react-hook-form. Its server is a backend-for-frontend: the browser calls only same-origin routes, and the BFF keeps the access token and the rotating refresh token in httpOnly cookies, refreshes them single-flight, checks the Origin of every write and forwards requests to the API with Authorization: Bearer. It also forwards the X-Workspace header the browser client sets from the /w/<slug>/ URL, and /admin, so Helm opens on the web app's origin with the same session.

The browser's API types are generated from the API's OpenAPI document: web/lib/api/schema.d.ts comes from the committed openapi.json. pnpm gen:api in web/ exports the document again and regenerates the types, so a changed DTO is a TypeScript error before it is a bug.

The generated layout

acme/
  • AGENTS.mdconventions for coding agents
  • CLAUDE.mdimports AGENTS.md
  • Cargo.toml
  • dock.tomllocal services: TimescaleDB, Mailpit
  • harbor.tomlservers and deploys
  • openapi.jsonthe API contract, checked in CI
  • .envthis machine's settings (git-ignored)
  • .env.example
  • .github/
    • workflows/
      • ci.ymlfmt, clippy, check, migrations, tests, OpenAPI drift, web lint, tests, build and journeys
  • config/
    • app.toml
  • src/
    • main.rs
    • lib.rs
    • app.rsthe root: platform() plus the kit's modules
    • platform.rsthe framework modules, configured once
    • abilities.rsrole permissions and ability rules
    • modules/
      • accounts/
      • settings/
      • projects/the example feature, copy it for your own
      • admin/
    • seed.rsthe demo workspace and accounts
  • tests/
    • app.rsboots the app, checks the module graph and the seeder
    • sign_up.rssign-up, verification and the first workspace
    • accounts.rssessions and sign-in options
    • projects.rsthe example feature, roles and isolation
    • settings.rsworkspace settings and notifications
    • billing.rscheckout and trials with the fake provider
  • web/
    • package.json
    • pnpm-lock.yamlcommitted: CI and deploys install from it
    • components.jsonshadcn/ui
    • .env.exampleNAVREE_API_URL and the real-time settings
    • .env.locala copy for this machine (git-ignored)
    • app/
      • (auth)/sign-in, sign-up, two-factor, verify, forgot and reset password
      • (app)/
        • w/[workspace]/dashboard, projects, settings
      • api/
        • [...path]/route.tsthe BFF: forwards with the bearer token
        • auth/login, two-factor, logout, refresh, email verification
      • admin/Helm, through the BFF
      • auth/social sign-in redirect and callback
      • broadcasting/
        • auth/route.ts
    • lib/
      • bff/the backend-for-frontend, unit-tested
      • api/the client, the generated schema and error helpers
      • realtime.tslive updates on the workspace's private channel
    • components/
      • ui/shadcn/ui components
    • e2e/Playwright journeys against the real API

Configuring it

Everything the kit needs is in config/app.toml and .env. The settings you will change first:

KeyIn the kitWhat it does
app.urlhttp://localhost:3000The web app's URL, for links in mail
auth.registrationtruePublic sign-up; set false to create accounts by invitation only
workspace.resolvers["header"]Where the workspace comes from: the browser client sends X-Workspace through the BFF
workspace.roles.*admin, member, viewerWhat each workspace role may do (projects.read, billing.manage, ...)
billing.providerfakefake for local development (checkout completes in the app); stripe or paddle in production, where harbor.toml sets APP_BILLING__PROVIDER=stripe
billing.plans.*starter, proYour plans, their trials and the provider's price ids
social.providers.*noneSocial sign-in providers; each one adds a button
mail.transportMailpit over SMTP, from .envYour mail provider in production

Secrets go in .env, never in the TOML file: APP_KEY, APP_BILLING__STRIPE__SECRET_KEY, APP_BILLING__STRIPE__WEBHOOK_SECRET and each social provider's client secret (APP_SOCIAL__PROVIDERS__GITHUB__CLIENT_SECRET).

The API kit

--kit api is the SaaS kit's API without web/: the same modules and the same tests, with its settings for API clients (app.url is the API's own address, and there is no CORS or front-end section).

  • Accounts over the API. Sign-up, verification, password reset and two-factor are JSON routes under /api/auth, with bearer tokens for every client. The OpenAPI document at /api/openapi.json describes all of them, and /api/docs renders it.
  • A committed contract. The kit ships openapi.json; navree openapi:export openapi.json writes it again, and the CI workflow fails when the committed file no longer matches the code, so every contract change shows in review. Generate typed clients from it. See API-first.

Use it for mobile apps, public APIs, or a front end you build in another stack. An API kit with a front end added later is the same app as the SaaS kit's API: the BFF pattern works with any web server.

The microservice kit

--kit micro creates one service that talks to others over a message broker, with navree-micro. It is a Cargo workspace of two crates, whose default member is the service, so cargo run, navree dev, navree check and every forwarded command run it from the root:

billing/
  • AGENTS.md
  • CLAUDE.md
  • Cargo.tomlthe workspace; default-members = the service
  • dock.tomlTimescaleDB and NATS
  • harbor.toml
  • .env.example
  • .github/
    • workflows/
      • ci.ymlfmt, clippy, check, migrations, patterns, tests
  • config/
    • app.toml[micro] transport = "nats"
  • crates/
    • billing-contracts/the messages and events, shared with clients
      • src/lib.rs
    • billing/
      • src/
        • main.rs
        • app.rsHttp and Observe (health and metrics), Auth, MicroModule, the feature modules
        • modules/
          • invoices/
            • handlers.rs#[message_pattern] and #[event_pattern]
            • service.rs
            • entity.rs
            • migrations/
      • tests/
        • app.rsboots the service, health checks, patterns
        • invoices.rshandlers called through MicroFake, no broker needed

The handlers are ordinary injectable providers in a module, so they share the container, the access rules and the unit of work with the rest of your code:

crates/billing/src/modules/invoices/handlers.rs

The module registers them with m.message_handlers::<InvoiceHandlers>(). draft_for_deal drafts one invoice per deal, so a repeated event changes nothing, and emits invoice.drafted with ServiceClient::emit, which the outbox publishes once the unit of work commits. An invalid event goes straight to the dead letters (navree micro:failed).

billing-contracts holds GetInvoice, Invoice, the invoices.get pattern (GET_INVOICE) and the events DealWon and InvoiceDrafted, each an Event whose NAME is its pattern. It depends on navree with the events feature only, so other services depend on it to call this one with ServiceClient and get the same types back:

Rust

The caller's bearer token travels with the message, and the service's AuthModule verifies it, so give every service the same auth.jwt.secret (APP_AUTH__JWT__SECRET). The service creates no accounts of its own (auth.registration = false).

The service also serves /health/live, /health/ready (with a micro check that fails while the broker is unreachable) and /metrics over HTTP, so an orchestrator can probe it, and serve starts the HTTP server and the message listener together (micro:listen runs the listener alone). The kit sets micro.transport = "nats" and turns on the micro-nats feature in crates/billing/Cargo.toml; switch to Kafka, RabbitMQ, Redis Streams, MQTT or TCP by changing both, without touching the handlers. See Microservices.

tests/support/mod.rs boots the service with the committed config/app.toml on a fresh SQLite database and replaces the broker with MicroFake's in-memory transport (override_value with MicroOptions built from micro.transport()), so micro.client().send(..) and .emit(..) go through the same pipeline as over NATS: authentication, validation, the unit of work and the outbox.

Every kit includes

  • AGENTS.md and CLAUDE.md, so a coding agent knows the project's layout, commands and conventions from its first prompt. They point at the agent skills for Navree's common tasks (navree skills:install copies them into the project; or npx skills add NavreeHQ/navree).
  • dock.toml for Dock: navree dock up starts the services the kit needs, and .env already holds their settings. The CI workflow runs the same database image as a service container.
  • harbor.toml for Harbor: navree harbor:provision prepares a server and navree deploy ships a release with a health check on /health/ready. In the SaaS kit, its [frontend] section deploys web/ in the same release as the API, so a page and the API it calls always come from the same build.
  • A CI workflow in .github/workflows/ci.yml: formatting, clippy, check (the module graph), the migrations on PostgreSQL and the Rust tests; for saas and api, the OpenAPI drift check; for micro, micro:patterns; for saas, the web app's lint, type check, unit tests and build, and the Playwright journeys against the real API.
  • Tests for what it ships. Rust tests boot the app with TestApp on a fresh database and use the fakes for mail, the queue, billing and the message broker. The SaaS kit's BFF and API client have unit tests (Vitest) against a scripted API, and its Playwright journeys cover sign-up, sign-in with two-factor, inviting a member, upgrading a plan through the fake checkout, and what members and viewers may do.

Customizing a kit

The code is yours from the first commit, so change it the way you change any Navree app:

  • Add features with navree make:module, then copy the shape of projects/: a workspace-scoped entity, the InWorkspace guard and CurrentWorkspace in the controller, an access subject and a page in web/app/(app)/w/[workspace]/. In the micro kit, run the generators in crates/<service>.
  • Remove what you don't need. Delete the module and its line in src/app.rs; for billing, also remove BillingModule from platform.rs, the billing screens under web/app/(app)/w/[workspace]/settings/billing/ and the billing tests. navree check tells you if anything still depends on it.
  • Restyle the front end. The colors, radius and fonts are CSS variables in web/app/globals.css, and the components in web/components/ui/ are shadcn/ui source files you edit directly. Add more with pnpm dlx shadcn@latest add <component> in web/: the app runs the shadcn CLI without depending on it, and the one stylesheet it used from the shadcn package is vendored as web/app/shadcn-tailwind.css.
  • Change roles and permissions in config/app.toml ([workspace.roles.*]) and src/abilities.rs. The front end reads the rules from GET /api/me/abilities and GET /api/v1/settings/abilities, so buttons follow the API's rules without front-end changes.

Updating

There are two kinds of updates, and neither rewrites your code.

The framework comes from the navree crate. Raise its version in Cargo.toml (or run cargo update -p navree), then follow the release notes. The web app's dependencies are ordinary npm packages.

The kit is a template, so improvements to it reach new apps, not yours. Your app records the kit it started from in Cargo.toml ([workspace.metadata.navree] in the micro kit's workspace):

Cargo.toml

To adopt a later kit's changes, generate a fresh copy with the current CLI and compare the parts you care about:

Shell

The release notes list what changed in each kit, so you can pick up a fix without reviewing the whole template.