A minimalist, domain-driven Rust API server template using Axum and SQLx.
Designed for clarity, scalability, and rapid development.
- Clean Architecture: Clear separation of domain, infrastructure, and API layers
- Modular Domains: Self-contained features (auth, user, device, file)
- SQLx Integration: Compile-time-checked queries in offline mode
- JWT Auth: Secure authentication and authorization
- File Uploads: Asynchronous handling and secure asset serving
- OpenAPI Docs: Swagger UI powered by Utoipa
- Observability: OpenTelemetry tracing and metrics
- Testing: Unit and integration tests with
tokio::test
andtower::ServiceExt
Recommended layout:
├── src/
│ ├── main.rs # Application entry point
│ ├── app.rs # Router setup and middleware
│ ├── lib.rs # Module declarations
│ ├── common.rs
│ ├── common/ # Shared components and utilities
│ │ ├── app_state.rs # AppState struct for dependency injection
│ │ ├── bootstrap.rs # Service initialization and AppState construction
│ │ ├── config.rs # Environment variable configuration loader
│ │ ├── dto.rs # Shared/global DTOs
│ │ ├── error.rs # AppError enum and error mappers
│ │ ├── hash_util.rs # Hashing utilities (e.g., bcrypt)
│ │ ├── jwt.rs # JWT encoding, decoding, and validation
│ │ ├── multipart_helper.rs # Multipart Helper
│ │ ├── opentelemetry.rs # OpenTelemetry setup
│ │ └── ts_format.rs # Custom timestamp serialization formatting
│ ├── domains.rs # Domain modules declarations
│ ├── domains/ # Feature modules
│ │ ├── <feature>/ # e.g., auth, user, device, file
│ │ │ ├── api/
│ │ │ │ ├── handlers.rs # Route handlers
│ │ │ │ └── routes.rs # Route definitions
│ │ │ ├── domain/ # Domain models, traits
│ │ │ │ ├── model.rs
│ │ │ │ ├── repository.rs
│ │ │ │ └── service.rs
│ │ │ ├── dto/ # Data Transfer Objects
│ │ │ │ └── <feature>_dto.rs
│ │ │ └── infra/ # Infrastructure-layer implementations
│ │ │ ├── impl_repository.rs
│ │ │ └── impl_service.rs
│ │ ├── <feature>.rs # Module entry point
├── tests/
│ ├── asset/
│ ├── test_helpers.rs # Shared setup and utilities for tests
│ └── test_<feature>_routes.rs
├── .env # Environment variables for local development
├── .env.test # Environment overrides for test environment
When adding a new feature module, register it in:
src/domains.rs
src/app.rs
src/common/app_state.rs
src/common/bootstrap.rs
- Rust (latest stable)
- PostgreSQL
- Docker & Docker Compose (optional)
Choose your preferred setup:
Using Docker Compose:
docker-compose up --build
To stop and clean up:
docker-compose down --rmi all
Manual Setup:
-
Create database tables and seed data:
db-seed/01-tables.sql db-seed/02-seed.sql
-
Configure environment variables in
.env
:DATABASE_URL=postgres://testuser:pass@localhost:5432/testdb JWT_SECRET_KEY=your_super_secret_key SERVICE_PORT=8080
-
Prepare SQLx offline mode with validation:
cargo sqlx prepare --check
-
Run the server locally:
cargo run
-
Login to obtain a JWT token:
curl -X POST http://localhost:8080/auth/login \ -H "Content-Type: application/json" \ -d '{"client_id":"apitest01","client_secret":"test_password"}'
-
Use the returned
token
to access protected endpoints:curl http://localhost:8080/user -H "Authorization: Bearer $token"
Open http://localhost:8080/docs in your browser for Swagger UI.
-
Authenticate via
/auth/login
(POST) with JSON payload:{ "client_id": "apitest01", "client_secret": "test_password" }
-
Copy the returned JWT token.
-
Click the 🔒 Authorize button in Swagger UI and paste the token to authorize requests.
- Domain: Traits and models define core business logic.
- Infra: Concrete implementations (SQLx repositories, services)
- API: Axum handlers and route definitions
- DTOs: Typed request/response contracts
- Bootstrap: Wires dependencies into
AppState
- Create
domains/<feature>/
withapi/
,domain/
,infra/
,dto/
- Register in
domains.rs
,app.rs
,common/app_state.rs
,common/bootstrap.rs
model.rs
: holds your core structs and enums that represent entities or value objects.- Model Type Reference: Conversions between Rust and PostgreSQL types.
See SQLx Postgres types mapping repository.rs
: declares the trait(s) that encapsulate persistence operations for the feature (e.g.,UserRepository
).service.rs
: declares the trait(s) for feature service operations.
Each feature owns its own impl_repository.rs
and impl_service.rs
sqlx::query
- Runtime-checked
- Flexibility: Handy when the SQL must be constructed dynamically—adding WHERE clauses on the fly, for instance.
sqlx::query!
- Compile-time-checked: The macro reads your SQL at build time (in “offline mode” if configured) and verifies it against your database schema. Mistyped column names or wrong argument types become compiler errors, not runtime surprises.
- Automatic type inference: You list your Rust values after the SQL string, and SQLx figures out how to map them to the placeholder types ($1, $2, …).
- Struct-level safety: If you use query_as!, it also confirms that the columns you select match the fields of your target struct.
- Route handlers accept DTOs, invoke feature logic, and return serialized responses.
- Each feature owns its own
routes.rs
andhandlers.rs
. - Supports asynchronous multipart file uploads with validation.
- Secure file serving validates user permissions and sanitizes file paths.
- Request and response DTOs reside in each feature's
dto.rs
. - Explicit mapping between DTOs and feature models.
- Uses
serde
and optionally the validator crate for input validation.
This project uses the simple_dto_mapper_derive::DtoFrom
derive macro
to automatically generate From
implementations for mapping between domain models and Data Transfer Objects (DTOs).
It reduces boilerplate by mapping fields with matching names and types, while allowing customization through attributes
for field renaming, type conversion, or transformation functions. This ensures consistent and maintainable
conversion logic between internal domain structures and API-facing DTOs.
- Domain service traits define business contracts.
- Concrete implementations live in
impl_service.rs
, constructed via factory methods. bootstrap.rs
wires services and buildsAppState
for dependency injection.
- domain_codegen project provides a code generator specifically designed for the clean_axum_demo project. It automatically generates the feature layer structure under gen/, which you can copy and customize as needed.
See the db-seed/
directory for table definitions and sample data.
The database structure is illustrated in the ERD:
- Swagger UI is available at
/docs
(powered by Utoipa). Open http://localhost:8080/docs in your browser for Swagger UI. - DTOs and endpoints are annotated for OpenAPI specification.
All endpoints return a consistent JSON envelope:
{
"status": 200,
"message": "success",
"data": { ... }
}
Implemented as:
ApiResponse<T>
– generic response wrapperRestApiResponse<T>
– wrapper implementing Axum'sIntoResponse
trait
See definitions in common/dto.rs
.
- Unit tests cover feature logic and core components.
- Integration tests exercise HTTP endpoints and database interactions.
- Use
#[tokio::test]
andtower::ServiceExt
for HTTP simulation. - Test assets and helpers are located in the
tests/
directory.
- Centralized
AppError
enum implementsIntoResponse
. - Errors map to appropriate HTTP status codes with JSON structure, e.g.:
{
"status": 400,
"message": "Invalid request data",
"data": null
}
Configure via .env
at the project root.
Set database URL, JWT secret, service port, and asset settings.
Example .env
:
DATABASE_URL=postgres://testuser:pass@localhost:5432/testdb
JWT_SECRET_KEY=your_super_secret_key
SERVICE_PORT=8080
This project supports distributed tracing, logging, and metrics via OpenTelemetry.
docker run --rm --name jaeger \
-e COLLECTOR_OTLP_ENABLED=true \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 5778:5778 \
-p 9411:9411 \
jaegertracing/jaeger:2.6.0
Access the Jaeger UI at http://localhost:16686.
-
Run with OpenTelemetry:
cargo run --features opentelemetry
-
Build with OpenTelemetry:
cargo build --features opentelemetry
For details, see the Jaeger Getting Started guide.
Contributions are welcome! Feel free to open issues, suggest improvements, or submit pull requests with your ideas.
- MIT License. See LICENSE for details.