Mini-Project - Adding Middleware & Error Handling
Learning Objectives: By the end of this lesson, you will be able to:
- Integrate middleware (logging, CORS) into an Axum project
- Implement custom error handling for robust API responses
- Apply best practices for production-ready Rust web APIs
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
What’s the most useful middleware or error handling feature you’ve used in an app? Share your experience and how it improved your project.
Lesson Overview
- Duration: 90 minutes
- Prerequisites: Basic Rust, Axum fundamentals, async/await
- Materials Needed: Rust toolchain, code editor, existing Axum mini-project
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- Middleware in Axum - What, why, and how
- Error handling strategies - Custom errors and responses
- Live coding: Adding logging, CORS, and error handling
- Hands-on practice and exercises
- Common pitfalls and troubleshooting
Success Criteria:
By the end of this session, you should be able to add and customize middleware, implement structured error handling, and improve the robustness of your Axum APIs.
3. Foundation Knowledge (15-20 minutes)
Core Concepts
Middleware
Middleware are components that sit between the request and your handler logic. They can log requests, handle CORS, authenticate users, and more.
Real-World Analogy: Think of middleware as airport security checks—every passenger (request) goes through them before reaching the gate (handler).
Why This Matters: Middleware helps you enforce security, observability, and consistency across your API without duplicating code.
Error Handling
Error handling is about catching problems and returning clear, helpful responses to clients. In Axum, you can define custom error types and map them to HTTP responses.
Real-World Analogy: Like a customer service desk that translates technical issues into friendly, actionable messages for users.
Why This Matters: Good error handling improves developer experience, API usability, and security.
Key Terminology
- Middleware: Code that runs before/after your main handler, often for cross-cutting concerns (e.g., logging, CORS).
- CORS (Cross-Origin Resource Sharing): A browser policy that controls which cross-origin web pages can read your API’s responses; it is not authentication.
- Custom Error Type: A user-defined error struct or enum that represents possible failures in your app.
4. Detailed Explanation and Examples (25-30 minutes)
Understanding Middleware and Error Handling in Axum
Middleware in Axum is added using .layer() on your Router. Common middleware includes logging (for observability), CORS (for security), and authentication. Error handling involves defining error types and converting them into HTTP responses, often with helpful messages and status codes.
Practical Applications
- Logging all requests for monitoring and debugging
- Restricting which origins browser scripts may use to read the API (not blocking non-browser clients)
- Returning clear error messages for invalid input or server issues
Common Misconceptions
- Misconception: Middleware is only for logging. Reality: Middleware can handle authentication, rate limiting, CORS, and more.
- Misconception: Returning
Result<T, E>is enough for error handling. Reality: You should map errors to meaningful HTTP responses for clients.
5. Live Coding Demonstration (20-25 minutes)
Example 1: Basic Middleware Setup
use axum::{Router, routing::get};
use tower_http::{trace::TraceLayer, cors::CorsLayer};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello" }))
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive());
// ...run the server...
}
Explanation:
TraceLayerlogs requests and responses for observability.CorsLayer::permissive()allows all origins (good for development, not production).- Middleware is added with
.layer().
Example 2: Custom Error Handling
use axum::{response::{IntoResponse, Response}, http::StatusCode};
struct AppError(String);
impl IntoResponse for AppError {
fn into_response(self) -> Response {
eprintln!("internal error: {}", self.0); // Use tracing in real applications.
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
}
}
async fn handler() -> Result<String, AppError> {
Err(AppError("Something went wrong".into()))
}
Key Points:
- Define a custom error type (
AppError). - Implement
IntoResponseto control the HTTP response. - Use
Result<T, E>in handlers for error propagation.
Example 3: Restricting CORS
use tower_http::cors::{CorsLayer, Any};
use axum::{Router, http::{self, header}};
let cors = CorsLayer::new()
.allow_origin("https://trusted.com".parse::<http::HeaderValue>().unwrap())
.allow_methods([http::Method::GET, http::Method::POST])
.allow_headers([header::CONTENT_TYPE]);
let app = Router::new()
// ...routes...
.layer(cors);
Debugging Demonstration
Show common errors and how to fix them:
// Common mistake: Not implementing IntoResponse for custom error
enum MyError {}
// ...
// Handler returns Result<T, MyError> but MyError doesn't implement IntoResponse
// Corrected version:
impl IntoResponse for MyError {
fn into_response(self) -> Response {
// ...
}
}
6. Hands-On Practice (30-40 minutes)
Exercise 1: Basic Practice (10 minutes)
Objective: Add logging and permissive CORS middleware to your Axum project.
Instructions:
- Add
tower-httpto your dependencies. - Use
TraceLayerandCorsLayer::permissive()in your app. - Test with a simple route.
Starter Code:
// ...existing Axum setup...
Solution:
use axum::{Router, routing::get};
use tower_http::{trace::TraceLayer, cors::CorsLayer};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "Hello" }))
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive());
// ...run the server...
}
Exercise 2: Intermediate Challenge (15 minutes)
Objective: Implement a custom error type and return structured error responses.
Requirements:
- Define a custom error struct or enum
- Implement
IntoResponsefor your error - Use it in a handler that can fail
Hints:
- Use
StatusCodeand a JSON body for errors - Leverage
serde_jsonfor serialization
Exercise 3: Creative Application (15 minutes)
Objective: Restrict CORS to only allow your frontend’s origin and log all errors.
Challenge:
- Configure
CorsLayerto allow onlyhttp://localhost:3000 - Add error logging in your custom error handler
Extension Ideas:
- Add authentication middleware
- Implement rate limiting
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Forgetting to implement
IntoResponsefor custom errors Cause: Axum can’t convert your error to an HTTP response Solution: Always implementIntoResponsefor custom error types -
Problem: Using permissive CORS in production Cause: Security risk—any site can access your API Solution: Restrict CORS to trusted origins
Debugging Tips
- Check logs for error details
- Use
curlor Postman to test CORS and error responses - Read error messages carefully—they often suggest the fix
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Use middleware for cross-cutting concerns (logging, CORS, auth)
- Return structured, consistent error responses (preferably JSON)
- Restrict CORS in production
Next Steps in Your Learning Journey
- Explore authentication and rate limiting middleware
- Learn about request validation and extracting data from requests
- Build a more complex API with robust error handling
9. Summary and Review (5 minutes)
Key Takeaways
- Middleware adds cross-cutting features to your API
- Custom error handling improves API usability and security
- Configure CORS for browser clients and separately authenticate and authorize API requests
Quick Review Questions
- What is middleware and why is it useful?
- How do you implement custom error handling in Axum?
- Why should you avoid permissive CORS in production?
For Instructors
Teaching Tips
- Encourage students to experiment with different middleware
- Discuss real-world scenarios for error handling
- Adjust timing based on student experience
Assessment Ideas
- Quiz: Identify and fix error handling bugs
- Project: Add middleware and error handling to a mini-project
- Peer review: Evaluate error responses and CORS settings
Adaptation Notes
- For beginners: Focus on logging and basic error handling
- For advanced: Add authentication, rate limiting, and custom middleware
- Online: Use live coding and screen sharing; In-person: Pair programming