astrea 0.0.2

A file-system based routing framework for Axum with automatic code generation
Documentation

Astrea

Crates.io Documentation License: MIT Rust Axum

A file-system based routing framework for Axum, inspired by Nitro and H3.

crates.io | GitHub | Documentation

中文文档

Features

  • Simple, unified function signature - All handlers follow the same pattern: async fn handler(event: Event) -> Result<Response>
  • Declarative parameter extraction - Access request data through helper functions instead of complex extractors
  • File-based routing - Routes are automatically generated from your filesystem structure
  • Type-safe - Full Rust type safety with compile-time route generation
  • Axum ecosystem compatible - Works seamlessly with Axum middleware and ecosystem
  • Zero runtime overhead - Route generation happens at compile time via procedural macros

Installation

Add this to your Cargo.toml:

[dependencies]
astrea = "0.0"
tokio = { version = "1", features = ["full"] }

Or use cargo-edit:

cargo add astrea

Quick Start

1. Create your route files

Create a routes/ directory in your project root and add route files:

routes/
├── index.get.rs          # GET /
├── users/
│   ├── index.get.rs      # GET /users
│   └── [id].get.rs       # GET /users/:id
└── posts/
    └── index.post.rs     # POST /posts

2. Define your handlers

// routes/index.get.rs
use astrea::prelude::*;

#[route]
pub async fn handler(_event: Event) -> Result<Response> {
    json(json!({
        "message": "Welcome to Astrea!",
        "version": "0.1.0"
    }))
}
// routes/users/[id].get.rs
use astrea::prelude::*;

#[route]
pub async fn handler(event: Event) -> Result<Response> {
    let user_id = get_param_required(&event, "id")?;
    json(json!({
        "user_id": user_id,
        "name": "Alice"
    }))
}

3. Generate routes and run

// src/main.rs
#[allow(dead_code, unused_imports)]
mod routes {
    astrea::generate_routes!();
}

#[tokio::main]
async fn main() {
    let app = routes::create_router();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();

    tracing::info!("Listening on http://127.0.0.1:3000");

    astrea::serve(listener, app).await.unwrap();
}

Route File Naming Convention

Routes are generated based on the file structure and naming:

File Pattern Route Method
index.get.rs / GET
index.post.rs / POST
users.get.rs /users GET
users/[id].get.rs /users/:id GET
posts/[...slug].get.rs /posts/*slug GET

Dynamic parameters use square brackets:

  • [id]:id (single path parameter)
  • [...slug]*slug (catch-all parameter)

Request Data Extraction

Astrea provides helper functions to access request data:

use astrea::prelude::*;

#[route]
pub async fn handler(event: Event, bytes: Bytes) -> Result<Response> {
    // Path parameters - Approach 1: get optional param
    let params = get_param(&event, "id"); // Option<&str>

    // Path parameters - Approach 2: get required param (returns error if missing)
    let user_id = get_param_required(&event, "id")?;

    // Query parameters - Approach 1: get the full query map
    let query = get_query(&event);
    let search = query.get("q").cloned().unwrap_or_default();

    // Query parameters - Approach 2: get a single query param directly
    let search = get_query_param(&event, "q"); // Option<String>

    // Request headers
    let auth = get_header(&event, "authorization");

    // JSON body
    #[derive(Deserialize)]
    struct CreateUserRequest {
        name: String,
        email: String,
    }
    let body: CreateUserRequest = get_body(&event, &bytes)?;

    json(json!({ "user_id": user_id }))
}

Response Helpers

use astrea::prelude::*;

// JSON response
json(json!({ "message": "Hello" }))?

// Text response
text("Hello, World!")

// HTML response
html("<h1>Hello</h1>")

// Redirect
redirect("/login")?

// Custom status and headers
json(json!({ "status": "created" }))?
    .status(StatusCode::CREATED)
    .header("X-Request-Id", "abc123")

Examples

Run the examples with cargo run --example <name>:

# Simple hello world
cargo run --example hello

# JSON API with route parameters
cargo run --example json_api

# Request data extraction (query params, headers, JSON body)
cargo run --example request_data

For a complete application example, see the basic-app/ directory.

Project Structure

astrea/
├── astrea-macro/         # Procedural macros
│   └── src/lib.rs        # #[route] and generate_routes! macros
├── basic-app/            # Complete example application (independent project)
│   └── src/routes/       # File-based route examples
├── examples/             # Simple code examples
│   ├── hello.rs          # Minimal hello world
│   ├── json_api.rs       # JSON API with parameters
│   └── request_data.rs   # Request data extraction
├── benches/              # Performance benchmarks
├── src/
│   ├── lib.rs            # Main library exports
│   ├── event.rs          # Event type
│   ├── extract.rs        # Helper extraction functions
│   ├── response.rs       # Response builders
│   ├── error.rs          # Error types
│   └── router.rs         # Router utilities
└── tests/                # Integration tests

Author

TNXG (Asahi Shiori)

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by Nitro and H3
  • Built on top of the excellent Axum framework
  • Part of the Rust web ecosystem