This project serves as an educational resource for learning how to build scalable and robust web applications using the Axum framework in Rust. It demonstrates modern web development practices with a focus on maintainability, security, and proper architecture.
This application was built as a learning project to understand:
- How to structure a professional Axum web application
- How to implement common web application patterns in Rust
- Best practices for authentication, error handling, and database integration
- How to create a maintainable and scalable codebase
- Authentication middleware that protects routes (
auth_middleware) - Session management with Tower Sessions
- Request ID generation and propagation for tracing
- Structured logging with the TraceLayer
- Flash message extractor for communicating between requests
- Form data extraction with validation
- Session data extraction and enhancement
- Custom user authentication with session-based login
- Password hashing with Argon2 (industry standard)
- HMAC signing for secure data
- Protected routes with middleware guards
- Centralized error type with conversions (
Errorenum) - Constraint-based database error mapping (
ResultExttrait) - User-friendly error messages and redirects
- Consistent error responses across the application
- Request tracing with unique request IDs
- Structured logging with tracing-subscriber
- Span-based context propagation
- Environment-based log filtering
- SQLx for type-safe database queries
- Migration management
- Connection pooling
- Repository pattern for database operations
- Server-side rendering with Askama templates
- Typed template contexts
- Reusable template components
- Integration testing of API endpoints
- Test helpers for common operations
- Isolated test environment
- Environment-specific configuration
- YAML-based configuration
- Strongly-typed settings with validation
βββ config/ # Configuration files
β βββ base.yaml # Base configuration
β βββ dev.yaml # Development environment config
βββ migrations/ # Database migrations
βββ scripts/ # Utility scripts
βββ src/
β βββ config.rs # Configuration loading
β βββ http/ # HTTP layer
β β βββ error.rs # Error handling
β β βββ tasks/ # Task-related endpoints
β β βββ users/ # User-related endpoints
β β βββ utilities.rs # Common HTTP utilities
β βββ lib.rs # Library entry point
β βββ logging.rs # Logging setup
β βββ main.rs # Application entry point
βββ templates/ # HTML templates
βββ tests/ # Integration tests
- Rust (stable) - Install Rust
- PostgreSQL - Install PostgreSQL
- Docker (optional, for containerized database) - Install Docker
-
Clone the repository:
git clone <repository-url> cd todo-axum-sqlx
-
Create a
.envfile in the project root with the following configuration:DATABASE_URL=postgres://postgres:password@localhost:5432/todo POSTGRES_USER=postgres POSTGRES_PASSWORD=password POSTGRES_DB=todo POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_CONTAINER_NAME=todo_db POSTGRES_IMAGE=postgres:14 APP_ENV=dev -
Start PostgreSQL database:
Option 1: Using the provided script (requires Docker):
chmod +x scripts/setup.sh ./scripts/setup.sh start
Option 2: Using your locally installed PostgreSQL:
createdb todo
-
Run database migrations:
cargo install sqlx-cli --no-default-features --features native-tls,postgres sqlx db setup
-
Build and run the application:
cargo run
-
Access the application: The application will be available at http://localhost:8000
cargo testThe project includes a custom authentication middleware (auth_middleware) that protects routes by checking for valid user sessions:
pub async fn auth_middleware(session: Session, mut req: Request, next: Next) -> Result<Response> {
match session.get::<UserSessionData>(UserSessionData::SESSION_KEY).await? {
Some(user_session_data) => {
req.extensions_mut().insert(user_session_data);
let response = next.run(req).await;
Ok(response)
}
None => Err(Error::Unauthorized),
}
}The project implements a central error type with conversions from various error sources:
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("an error occurred with the databse")]
SQLx(#[from] sqlx::Error),
#[error("an internal server error occurred")]
Other(#[from] anyhow::Error),
#[error("entity not found")]
NotFound,
// ... other error variants
}The project uses Argon2 (a memory-hard hashing algorithm) for secure password storage:
async fn hash_password(password: &SecretString) -> Result<String> {
let salt = SaltString::generate(&mut OsRng);
Ok(Argon2::default()
.hash_password(password.expose_secret().as_bytes(), &salt)?
.to_string())
}SQLx provides compile-time checked SQL queries:
pub async fn get_all_tasks(pool: &PgPool, user_id: Uuid) -> Result<Vec<Task>> {
sqlx::query_as!(
Task,
r#"
select * from task
where user_id = $1
"#,
user_id
)
.fetch_all(pool)
.await
.map_err(Error::SQLx)
}This project is designed for learning purposes and demonstrates several advanced concepts:
-
Middleware Composition: Learn how to compose multiple middleware layers for cross-cutting concerns like authentication, logging, and error handling.
-
Error Handling Strategy: Study the centralized error handling approach for creating consistent user experiences.
-
Async Programming: See real-world examples of asynchronous Rust in a web application context.
-
Security Best Practices: Learn proper password hashing, session management, and authentication flows.
-
Database Patterns: Understand how to organize database interactions in a maintainable way.
As you explore this codebase, consider extending it with:
- Rate limiting middleware
- CSRF protection
- Request validation using a validation library
- API documentation with OpenAPI/Swagger
- User roles and permissions
- Metrics collection and monitoring
- Caching strategies
- WebSocket integration