| Crates.io | dog-axum |
| lib.rs | dog-axum |
| version | 0.1.4 |
| created_at | 2026-01-23 03:55:16.626079+00 |
| updated_at | 2026-01-25 03:37:40.911928+00 |
| description | Axum web framework integration for DogRS - HTTP handlers, middleware, and web utilities |
| homepage | |
| repository | https://github.com/Jitpomi/dogrs |
| max_upload_size | |
| id | 2063357 |
| size | 77,314 |
A high-level REST framework built on Axum that provides service-oriented architecture with pluggable middleware support.
Create a minimal dog-axum server:
use dog_axum::AxumApp;
use dog_core::{DogApp, DogService};
use std::sync::Arc;
// Define your request/response types
#[derive(serde::Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
}
#[derive(serde::Serialize)]
struct User {
id: u32,
name: String,
email: String,
}
// Create a service
struct UserService;
#[async_trait::async_trait]
impl DogService<CreateUserRequest, ()> for UserService {
async fn create(&self, _tenant: TenantContext, data: CreateUserRequest) -> Result<User> {
Ok(User {
id: 1,
name: data.name,
email: data.email,
})
}
}
// Build the server
#[tokio::main]
async fn main() -> Result<()> {
let app = DogApp::new();
let user_service = Arc::new(UserService);
let server = AxumApp::new(app)
.use_service("/users", user_service)
.service("/health", || async { "ok" });
server.listen("0.0.0.0:3030").await
}
Apply middleware to specific services:
use dog_axum::{AxumApp, middlewares::MultipartToJson};
use tower::ServiceBuilder;
let server = AxumApp::new(app)
// Single middleware
.use_service_with("/upload", upload_service, MultipartToJson::default())
// Multiple middleware with ServiceBuilder
.use_service_with("/api", api_service,
ServiceBuilder::new()
.layer(CorsMiddleware::permissive())
.layer(AuthenticationMiddleware::new())
.layer(RateLimitingMiddleware::new(100))
)
// Service without middleware
.use_service("/health", health_service);
Apply middleware to all routes:
let mut server = AxumApp::new(app)
.use_service("/users", user_service)
.use_service("/posts", post_service);
// Add global middleware to the router
server.router = server.router
.layer(axum::extract::DefaultBodyLimit::max(100 * 1024 * 1024))
.layer(tower_http::cors::CorsLayer::new()
.allow_origin(tower_http::cors::Any)
.allow_methods(tower_http::cors::Any)
.allow_headers(tower_http::cors::Any)
);
Handles multipart form uploads and converts them to JSON with BlobRef pattern for files:
use dog_axum::middlewares::{MultipartToJson, MultipartConfig};
let config = MultipartConfig::default()
.with_max_file_size(50 * 1024 * 1024) // 50MB per file
.with_max_total_size(200 * 1024 * 1024); // 200MB total
let server = AxumApp::new(app)
.use_service_with("/upload", upload_service,
MultipartToJson::with_config(config)
);
BlobRef Pattern: Files are streamed to temporary storage and services receive references:
Visual BlobRef Flow:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Multipart │ │ MultipartToJson│ │ Temp Storage │ │ Service │
│ Upload │───▶│ Middleware │───▶│ /tmp/file_* │───▶│ Gets BlobRef │
│ (7MB file) │ │ │ │ │ │ (not raw data) │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Streams chunks │ │ File written to │ │ JSON with │
│ (no memory │ │ disk (not RAM) │ │ file reference │
│ buffering) │ │ │ │ (framework-safe)│
└─────────────────┘ └─────────────────┘ └─────────────────┘
// Service receives this JSON structure for file uploads:
{
"name": "John Doe",
"avatar": {
"key": "temp/uuid",
"temp_path": "/tmp/multipart_file_uuid",
"filename": "avatar.jpg",
"content_type": "image/jpeg",
"size": 1024000
}
}
// Your service can then process the file:
#[derive(serde::Deserialize)]
struct CreateUserRequest {
name: String,
avatar: Option<BlobRef>,
}
#[derive(serde::Deserialize)]
struct BlobRef {
temp_path: String,
filename: Option<String>,
content_type: Option<String>,
size: u64,
}
Create custom middleware using Tower patterns:
use tower::{Layer, Service};
use axum::{response::Response, body::Body};
#[derive(Clone)]
pub struct LoggingMiddleware;
impl<S> Layer<S> for LoggingMiddleware {
type Service = LoggingService<S>;
fn layer(&self, inner: S) -> Self::Service {
LoggingService { inner }
}
}
#[derive(Clone)]
pub struct LoggingService<S> {
inner: S,
}
impl<S> Service<Request<Body>> for LoggingService<S>
where
S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
S::Future: Send,
{
type Response = Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn call(&mut self, req: Request<Body>) -> Self::Future {
println!("Request: {} {}", req.method(), req.uri());
let future = self.inner.call(req);
Box::pin(async move {
let response = future.await?;
println!("Response: {}", response.status());
Ok(response)
})
}
}
Middleware executes in reverse order of how it's added:
ServiceBuilder::new()
.layer(CorsMiddleware::new()) // Executes 1st (outermost)
.layer(AuthMiddleware::new()) // Executes 2nd
.layer(RateLimitMiddleware::new()) // Executes 3rd
.layer(MultipartToJson::default()) // Executes 4th (innermost)
Visual Request Flow:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Request │───▶│ CORS Layer │───▶│ Auth Layer │───▶│ RateLimit │───▶│ Multipart │
│ (Client) │ │ (Outermost) │ │ │ │ Layer │ │ Layer │
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Response │◀───│ CORS Layer │◀───│ Auth Layer │◀───│ RateLimit │◀───│ Service │
│ (Client) │ │ │ │ │ │ Layer │ │ (Business) │
└─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘ └─────────────┘
See the dog-examples/ directory for complete examples:
Add to your Cargo.toml:
[dependencies]
dog-axum = "0.1.0"
dog-core = "0.1.0"
axum = "0.7"
tower = "0.4"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
dog-axum provides a service-oriented architecture where:
DogService trait)Visual Architecture:
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ dog-axum │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Client │───▶│ Router │───▶│ Middleware │───▶│ Service │ │
│ │ (HTTP Req) │ │ (Axum) │ │ (Tower) │ │ (Business) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Auto Routes │ │ Per-Service │ │ Typed Data │ │
│ │ GET/POST/ │ │ or Global │ │ Serde JSON │ │
│ │ PUT/DELETE │ │ Layers │ │ Validation │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ Built on Tower + Axum │
└─────────────────────────────────────────────────────────────────────────────────────┘
Service Registration Flow:
AxumApp::new(app)
│
├─ .use_service("/users", user_service)
│ └─ Creates: GET/POST/PUT/DELETE /users routes
│
├─ .use_service_with("/upload", upload_service, MultipartToJson)
│ └─ Creates: Routes + applies middleware to this service only
│
└─ .service("/health", health_handler)
└─ Creates: Custom route with handler function
This separation allows for clean, testable code with flexible middleware composition.