When you run suprnova new my-app --frontend svelte, the scaffolder
gives you this:
my-app/
├── Cargo.toml # crate manifest + dependencies, two [[bin]] targets
├── .env # local config - DB URL, app key, ports
├── .env.example # template for ops/CI
├── .gitignore # excludes target/, .env, node_modules/, public/assets/
├── cmd/
│ └── main.rs # the binary entry; calls Application::new().run()
├── src/
│ ├── lib.rs # module wiring (`pub mod controllers;` etc.)
│ ├── bootstrap.rs # registers services, observers, listeners - the
│ │ # Suprnova analogue of Laravel's service providers
│ ├── routes.rs # the `routes!` macro tree - every URL the app serves
│ ├── bin/
│ │ └── console.rs # `cargo run --bin console <subcommand>` entry -
│ │ # the Suprnova analogue of `php artisan`
│ ├── actions/
│ │ ├── mod.rs
│ │ └── example_action.rs # one-method invokable controllers
│ ├── commands/
│ │ └── mod.rs # `#[command]`-annotated handlers register here
│ ├── config/
│ │ ├── mod.rs
│ │ ├── database.rs # typed DB config (driver, URL, pool)
│ │ └── mail.rs # typed mail config
│ ├── controllers/
│ │ ├── mod.rs
│ │ ├── home.rs # GET / handler
│ │ ├── auth.rs # login / register / logout
│ │ └── dashboard.rs # requires auth; example protected route
│ ├── middleware/
│ │ ├── mod.rs
│ │ ├── logging.rs # request/response logging
│ │ └── authenticate.rs # session-based auth guard
│ ├── migrations/
│ │ ├── mod.rs
│ │ ├── m_*_create_users_table.rs
│ │ ├── m_*_create_sessions_table.rs
│ │ ├── m_*_create_remember_tokens_table.rs
│ │ ├── m_*_create_workflows_table.rs
│ │ └── m_*_create_workflow_steps_table.rs
│ └── models/
│ ├── mod.rs
│ └── user.rs # `#[suprnova::model]` User model
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json
│ ├── index.html # Vite entry; mounts the SPA
│ └── src/
│ ├── main.{tsx,ts} # Inertia client setup (per-framework)
│ ├── app.css # global styles + Tailwind
│ ├── pages/
│ │ ├── Home.{tsx,svelte,vue}
│ │ ├── Dashboard.{tsx,svelte,vue}
│ │ └── auth/
│ │ ├── Login.{tsx,svelte,vue}
│ │ └── Register.{tsx,svelte,vue}
│ └── types/
│ └── inertia-props.ts # auto-generated from #[derive(InertiaProps)]
└── public/
└── assets/ # Vite production build output lands here
Svelte adds frontend/svelte.config.js and frontend/src/app.d.ts.
Vue adds frontend/src/shims-vue.d.ts.
The API starter (suprnova new my-api --api) is slimmer: no
frontend/, no auth controllers, and cmd/main.rs is replaced by
src/main.rs.
What each directory is for
cmd/main.rs
The binary entry point. A short file - typically 10–20 lines - that calls the standard boot pipeline:
use Application;
use ;
async
Application::run() parses the binary's CLI (serve / web:run /
migrate* / schedule:* / workflow:work / queue:work), loads
.env, runs your config function, then dispatches the subcommand. The
serve path also runs your bootstrap function and starts the HTTP
server.
You almost never edit cmd/main.rs after the initial scaffold.
src/lib.rs
A flat module declaration file:
This is what makes crate::controllers::home::index reachable from
routes.rs.
src/bootstrap.rs
The single function that wires your app. You register service container
bindings, observers, event listeners, custom middleware, and any other
boot-time setup here. It's the analogue of Laravel's AppServiceProvider,
EventServiceProvider, BroadcastServiceProvider, etc., all in one
file:
use Arc;
use App;
pub async
register() runs once per process, after the config loader but before
serve accepts the first request. Workers (queue:work,
schedule:run, workflow:work) reuse the same bootstrap so they see
the same services. See Application Bootstrap.
src/routes.rs
Your URL surface. The routes! macro at module top-level expands into
a pub fn register() -> Router that cmd/main.rs hands to
Application::routes(...):
use ;
use crate::;
routes!
See Routing.
src/bin/console.rs
Your per-project console binary. Runs as cargo run --bin console <subcommand> and dispatches the framework's db:seed built-in plus
every #[command]-annotated handler (or #[derive(Command)] typed
struct) in src/commands/ - both forms register through inventory at
compile time:
The long-running workers (queue:work, schedule:run,
schedule:work, workflow:work) live on the main app binary
because Application::run() dispatches them - call them as
cargo run -- queue:work (or via suprnova schedule:run /
suprnova workflow:work if you prefer the umbrella CLI).
See Console.
src/commands/
Where your console handlers live. Two flavours: a typed struct with
clap-derived args and impl TypedCommand, or a raw #[command] on an
async fn(Vec<String>) -> Result<(), FrameworkError>. The scaffolder
generates the typed form:
use async_trait;
use Parser;
use ;
suprnova make:command report-daily scaffolds the file and adds it to
src/commands/mod.rs. See Console.
src/config/
Typed configuration structs. The scaffold ships database.rs and
mail.rs; add your own for any subsystem your app cares about. Each
config struct reads its values from the environment, and
config::register_all() registers them with the framework:
use ;
Wire it in config/mod.rs:
use Config;
See Configuration.
src/controllers/
HTTP handler functions. One module per resource. Each pub async fn
that takes a Request and returns a Response is callable from a
route.
src/middleware/
Middleware implementations. The scaffold ships logging and
authenticate; you add your own here as pub struct Foo with
impl Middleware for Foo. Register them globally in bootstrap.rs
or apply per-route via .middleware(…) in the routes! tree. See
Middleware.
src/migrations/
SeaORM migrators. The scaffold ships a handful for the auth + workflow
tables. suprnova make:migration <name> adds a new one. suprnova migrate, migrate:rollback, migrate:status, migrate:fresh,
db:sync all operate on this directory. See Migrations.
src/models/
Your Eloquent models. One file per model, each a #[suprnova::model]
struct. The scaffold ships user.rs; add new models by writing a new
file by hand or running suprnova db:sync --regenerate-models after a
schema migration. See Eloquent.
src/actions/
Single-method invokable controllers. Optional pattern - use them when a controller would have exactly one method and you'd rather call it "Action" than wrap it. The scaffold ships an example you can delete or adapt. See Actions.
frontend/
The Vite + Inertia SPA. This is a normal frontend project - package.json,
vite.config.ts, tsconfig.json, an index.html Vite entry, source
under src/. The Inertia client setup lives in src/main.{tsx,ts} and
the page components in src/pages/. TypeScript types for your Rust
#[derive(InertiaProps)] props are regenerated into
src/types/inertia-props.ts by suprnova generate-types.
See Frontend.
public/assets/
Where Vite drops the production build (npm run build). The Suprnova
server serves this directory as static assets at /assets/* in
production.
Directories you'll add as the app grows
The scaffold gives you the minimum - enough to ship the welcome flow and a protected dashboard. Real apps grow more subsystems. Common additions:
| Directory | When you add it |
|---|---|
src/jobs/ |
First time you Queue::push(SomeJob). See Queues. |
src/listeners/ |
First time you Event::listen. See Events. |
src/observers/ |
First time you implement Observer<MyModel>. See Eloquent. |
src/notifications/ |
First time you implement a Notification. See Notifications. |
src/mail/ |
First time you implement a Mailable. See Mail. |
src/policies/ |
First time you write a #[policy]. See Authorization. |
src/factories/ |
First time you write a Factory<Model> for tests. See Eloquent Factories. |
src/seeders/ |
First time you write a Seeder for db:seed. See Seeding. |
src/events/ |
First time you impl Event for your own event type. See Events. |
src/broadcasting/ |
First time you define a private/presence Channel. See Broadcasting. |
src/ws/ |
First time you write a ws!() handler. See WebSockets. |
src/supervisors/ |
First time you implement a long-running Supervisor. See Supervisors. |
src/payments/ |
First time you wire up Stripe/Paddle for your app. See Payments. |
src/props/ |
When you want to keep #[derive(InertiaProps)] structs separate from controllers. |
resources/views/ |
First time you add a Tera template for mail bodies. |
storage/ |
First time you write files to the local filesystem disk (see File Storage). |
tests/ |
First time you write an integration test. |
You don't have to ask permission - mkdir src/jobs and add
pub mod jobs; to src/lib.rs, and you're done. The framework
doesn't enforce the directory names; the conventions exist so other
Suprnova developers can find things quickly.
The dogfood app/ in this repo
If you're reading this from inside the Suprnova repo itself, you'll
see an app/ directory at the root that uses every framework feature
together. That's our internal test bed - it exercises payments,
broadcasting, web push, workflows, supervisors, etc. all at once. It's
NOT a clean reference for a new app; the scaffold output above is
deliberately smaller and easier to learn from. Read app/ once you
want to see a maximal example of how the pieces compose.
Next
- Configuration - how
.envbecomes typed config - Application Bootstrap - what
bootstrap.rsactually does - Routing - your first route
- Service Container - how
App::bindandApp::getwork
