
handle errors properly in Axum
How to Handle Errors Properly in Axum (Without Making a Mess)
Comments (4)
nice dost
thank you everyone, for reading this blog.
nice to know about axum error handing
nice dost

How to Handle Errors Properly in Axum (Without Making a Mess)
nice dost
thank you everyone, for reading this blog.
nice to know about axum error handing
nice dost
When you first build an API in Rust with Axum, error handling feels simple:
async fn handler() -> Result<Json<User>, StatusCode> {}
But as your app grows, this quickly becomes messy:
So how do you structure this cleanly?
Let's walk through a real-world pattern.
Imagine a simple endpoint
POST /users
It:
But:
That's the challenge.
Instead of returning random status codes, we define one central error type:
enum AppError {
JsonRejection(JsonRejection),
TimeError(time_library::Error),
}
Now our whole application speaks one error language.
This is important.
You never want 20 different error types leaking into handlers.
? Work AutomaticallyInside the handler:
let created_at = Timestamp::now()?;
But wait...
Timestamp::now()` returns:
Result<Timestamp, time_library::Error>
Our handler returns:
Result<_, AppError>
How does ? know what to do ?
Because we implemented
impl From<time_library::Error> for AppError
This is the magic
When ? sees an error, it automatically converts it using From.
Under the hood, this:
Timestamp::now()?;
Becomes:
match Timestamp::now() {
Ok(val) => val,
Err(e) => return Err(AppError::from(e)),
}
This is clean, powerful and idiomatic Rust.
Now comes the most important pieces:
impl IntoResponse for AppError
This tells Axum: "If a handler returns
Err(AppError)
```, here's how to turn it into an HTTP response."
Inside, we seperate two world:
1. Client Errors (Bad JSON)
````rust
AppError::JsonRejection(rejection)
We:
Because the client messed up
AppError::TimeError(_)
We:
Never leak internal errors to users.
That's production mindset.
Axum’s default Json<T> returns plain text on error.
We want consistent JSON errors.
So we wrapt it:
#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(AppError))]
struct AppJson<T>(T);
Now JSON errors automatically becomes AppError
This keeps formatting consistent across the app.
Let's say the time library fails:
Here's what happens:
rust Timestamp::now()? converts error -> rust AppErrorrust Err(AppError)rust into_response()Client sees:
{
"message": "Something went wrong"
}
Server logs:
error from time_library err=failed to get time
That's exactly what we want.
This pattern gives you:
It scales beautifully.
The real hero in this entire design is:
impl From<SomeError> for AppError
That single implementation unlocks:
? usageRust's error system isn't just strict.
It's composable.
Beginner Rust code looks like this:
if let Err(e) = something() {
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
even i wrote this type of code, now I am going to understand and write clean code.
Professional Rust code looksl ike this:
rust something()?
With:
rust From implementationsThat's the difference between "it works" and "it's designed".
Thank you for reading this blog.
References: Axum Anyhow Error Example