dynami

Crates.iodynami
lib.rsdynami
version0.1.0
created_at2026-01-07 06:24:10.503668+00
updated_at2026-01-07 06:24:10.503668+00
descriptionAutomatic Axum router generation from directory structure with file-system based routing
homepagehttps://github.com/ErdemGKSL/dynami
repositoryhttps://github.com/ErdemGKSL/dynami
max_upload_size
id2027611
size95,187
Erdem (ErdemGKSL)

documentation

https://docs.rs/dynami

README

Dynami

A Rust library for automatic Axum router generation from directory structure. Generate type-safe routers for Axum 0.8+ by organizing your route handlers into a folder hierarchy.

Features

  • File-system based routing - Routes are automatically generated from your folder structure
  • Soft overwriting - Preserves your custom code outside of generated sections
  • Smart route detection - Won't overwrite routes you've manually defined
  • Dynamic routes - Use d_ prefix for path parameters (e.g., d_id -> /{id})
  • Wildcard routes - Support for multi-segment captures (e.g., d_*rest -> /{*rest})
  • State detection - Automatically detects and applies your AppState type
  • All HTTP methods - GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
  • Auto-generated handlers - Creates default handlers for empty method files

Installation

Add to your Cargo.toml:

[dependencies]
dynami = "0.1"

[build-dependencies]
dynami = "0.1"

Usage

1. Create a folder structure for your routes

src/routes/
├── mod.rs           # Root router (optional AppState here)
├── get.rs           # GET /
├── post.rs          # POST /
└── api/
    ├── mod.rs       # API router
    ├── get.rs       # GET /api
    └── d_id/
        ├── mod.rs   # Dynamic router
        └── get.rs   # GET /api/{id}

2. Create a build.rs file

use dynami::format_routes;

fn main() {
    format_routes("./src/routes").unwrap();
    println!("cargo:rerun-if-changed=src/routes");
}

3. Run your build

cargo build

The library will generate:

  • router() functions in each mod.rs
  • pub mod declarations for subdirectories
  • Default handlers for empty method files
  • Proper type annotations with your AppState

4. Use the generated router in your application

mod routes;

#[tokio::main]
async fn main() {
    let app = routes::router();
    
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    
    axum::serve(listener, app).await.unwrap();
}

How it Works

Generated Code with dynami::generate!

The library uses the dynami::generate! macro to mark generated code blocks:

use axum::Router;
use axum::routing::get;

pub fn router() -> Router {
    let mut router = Router::new();
    
    // You can add custom routes here - they won't be overwritten
    router = router.route("/custom", get(custom_handler));
    
    dynami::generate! {
        // This block is auto-generated - don't edit manually
        router = router.route("/", get(get::handler));
        router = router.nest("/api", api::router());
    }
    
    router
}

Key benefits:

  • Add custom routes before or after the generated block
  • The generator detects your manual routes and won't duplicate them
  • Smart import generation: only imports the HTTP methods actually used (e.g., use axum::routing::{get, post} only if both are used)
  • If you remove dynami::generate!, the file won't be updated anymore
  • The macro is a simple pass-through that returns its input, so the code compiles normally

HTTP Method Files

Create files named after HTTP methods:

  • get.rs -> GET handler
  • post.rs -> POST handler
  • put.rs -> PUT handler
  • delete.rs -> DELETE handler
  • patch.rs -> PATCH handler
  • options.rs -> OPTIONS handler
  • head.rs -> HEAD handler

Dynamic Routes

Use the d_ prefix for path parameters:

  • d_id/ -> /{id} route
  • d_user_id/ -> /{user_id} route
  • d_*rest/ -> /{*rest} wildcard route (captures remaining path)

AppState Detection

Add your state struct to the root mod.rs:

#[derive(Clone)]
pub struct AppState {
    pub db: Database,
}

pub fn router() -> Router<AppState> {
    let mut router = Router::new();
    dynami::generate! {
        router = router.route("/", get(get::handler));
    }
    router
}

The library will:

  1. Detect the AppState struct (or any struct with "State" in its name)
  2. Generate Router<AppState> for all routers
  3. Include State extractors in default handlers

Examples

Simple API

routes/
├── mod.rs
├── get.rs        # GET / - list all
└── post.rs       # POST / - create new

Generates:

use axum::Router;
use axum::routing::{get, post};

pub fn router() -> Router {
    let mut router = Router::new();
    dynami::generate! {
        router = router.route("/", get(get::handler).post(post::handler));
    }
    router
}

Nested Routes

routes/
├── mod.rs
├── get.rs
└── api/
    ├── mod.rs
    ├── get.rs
    └── users/
        ├── mod.rs
        └── get.rs

Generates in routes/mod.rs:

pub mod api;

use axum::Router;
use axum::routing::get;

pub fn router() -> Router {
    let mut router = Router::new();
    dynami::generate! {
        router = router.route("/", get(get::handler));
        router = router.nest("/api", api::router());
    }
    router
}

Mixing Manual and Generated Routes

// routes/mod.rs
use axum::Router;
use axum::routing::get;

pub mod api;

// Custom handler defined locally
async fn health_check() -> &'static str {
    "OK"
}

pub fn router() -> Router {
    let mut router = Router::new();
    
    // Manual route - generator will detect this and skip generating GET /
    router = router.route("/health", get(health_check));
    
    dynami::generate! {
        // Only routes not already defined above will be generated
        router = router.nest("/api", api::router());
    }
    
    router
}

With AppState

// routes/mod.rs
use sqlx::PgPool;

#[derive(Clone)]
pub struct AppState {
    pub pool: PgPool,
}

pub fn router() -> Router<AppState> {
    let mut router = Router::new();
    dynami::generate! {
        router = router.route("/", get(get::handler));
    }
    router
}
// routes/get.rs
use axum::extract::State;
use axum::response::IntoResponse;
use super::AppState;

pub async fn handler(State(state): State<AppState>) -> impl IntoResponse {
    // Use state.pool here
    "Hello World"
}

Default Handlers

Empty method files get default handlers:

// Without state
use axum::response::IntoResponse;

pub async fn handler() -> impl IntoResponse {
    "OK"
}
// With state (when AppState is detected)
use axum::extract::State;
use axum::response::IntoResponse;

pub async fn handler(State(state): State<AppState>) -> impl IntoResponse {
    "OK"
}

Best Practices

  1. Run in build.rs - Generate routes during build time
  2. Keep the dynami::generate! block - If you remove it, the file won't be updated
  3. Add custom code outside the generate block - Your code won't be overwritten
  4. Use meaningful folder names - They become route paths
  5. Implement handlers - Replace default "OK" responses with real logic
  6. Define custom routes before the generate block - They'll be detected and not duplicated

Limitations

  • Axum doesn't support regex in path parameters (use middleware for validation)
  • Route generation happens at build time (requires rebuild for new routes)
  • Wildcard routes must use d_* prefix (e.g., d_*rest)

License

This project is open source and available under the MIT License.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Commit count: 0

cargo fmt