toni
Getting Started

Project Structure

How to organize a Toni application as it grows.

Toni doesn't enforce a specific file layout, but the module system naturally guides you toward a feature-based structure. Here's a recommended layout for a medium-to-large application:

src/
├── main.rs                    # Entry point — ToniFactory::create
├── app_module.rs              # Root module that imports feature modules

├── users/
│   ├── mod.rs                 # re-exports
│   ├── users_module.rs        # #[module] definition
│   ├── users_controller.rs    # #[controller] with route handlers
│   ├── users_service.rs       # #[injectable] business logic
│   └── dto/
│       ├── create_user.rs     # CreateUserDto
│       └── update_user.rs     # UpdateUserDto

├── auth/
│   ├── mod.rs
│   ├── auth_module.rs
│   ├── auth_controller.rs
│   ├── auth_service.rs
│   └── guards/
│       └── jwt_guard.rs       # #[guard] / Guard impl

├── database/
│   ├── mod.rs
│   ├── database_module.rs     # Global module exporting DatabaseService
│   └── database_service.rs

└── common/
    ├── filters/
    │   └── http_exception_filter.rs
    ├── interceptors/
    │   └── logging_interceptor.rs
    └── pipes/
        └── validation_pipe.rs

One feature per module

The key organizing principle: each feature is a module. A module owns its controllers and services. What it needs from other modules it declares as imports. What it offers to other modules it declares in exports.

// database/database_module.rs
#[module(
    providers: [DatabaseService],
    exports: [DatabaseService],   // other modules can inject this
)]
pub struct DatabaseModule;

// users/users_module.rs
#[module(
    imports: [DatabaseModule],    // now DatabaseService is available here
    controllers: [UsersController],
    providers: [UsersService],
)]
pub struct UsersModule;

Root module

The root module is the entry point for the module graph. It typically only imports feature modules — it doesn't own controllers or services itself.

// app_module.rs
#[module(
    imports: [
        DatabaseModule,
        UsersModule,
        AuthModule,
        ProductsModule,
    ],
)]
pub struct AppModule;

Growing the structure

As the application grows, feature modules can themselves become module groups:

src/
├── orders/
│   ├── orders_module.rs        # imports: [InventoryModule, PaymentsModule]
│   ├── orders_controller.rs
│   ├── orders_service.rs
│   ├── inventory/
│   │   ├── inventory_module.rs
│   │   └── inventory_service.rs
│   └── payments/
│       ├── payments_module.rs
│       └── payments_service.rs

Shared/common code

Infrastructure that spans features — interceptors, guards, pipes, common types — belongs in a shared location (common/ or shared/). These don't need their own modules; they're just Rust structs implementing the relevant trait, instantiated where needed or provided through a shared module.

// common/interceptors/logging_interceptor.rs
use toni::{async_trait, Interceptor, Context};

pub struct LoggingInterceptor;

#[async_trait]
impl Interceptor for LoggingInterceptor {
    async fn intercept(&self, ctx: &mut Context, next: Box<dyn InterceptorNext>) {
        println!("→ {} {}", ctx.method(), ctx.path());
        next.run(ctx).await;
        println!("← {}", ctx.status());
    }
}

Register globally at startup:

let mut factory = ToniFactory::new();
factory.use_global_interceptors(Arc::new(LoggingInterceptor));
let mut app = factory.create_with(AppModule, AxumAdapter::new()).await;

On this page