Introduction
What Does “Standardized Error Response” Mean?
Why Standardized Errors Are Important
Step 1: Create a Standard Error Struct
Step 2: Create Helper Functions for Common Errors
Step 3: Replace Raw Error Returns in Handlers
Step 4: Handle 401 Unauthorized Properly
Step 5: Handle 403 Forbidden
Step 6: Handle 404 Not Found
Step 7: Handle Validation Errors (422)
Step 8: Handle Internal Server Errors
Advanced: Global Error Handling Middleware
Standard Success Response (Optional Improvement)
Production Example: Final Structure
Why This Prevents Security Leaks
Common Developer Mistakes
Real Enterprise Pattern
Add Timestamp (Optional Advanced)
Add Request ID (Advanced Observability)
Final Architecture After Standardization
Summary
Introduction
In many projects, error responses look like this:
401 → "Unauthorized"
403 → "Access denied"
500 → HTML page
Validation error → raw text
DB error → panic message
This creates:
Inconsistent frontend handling
Hard debugging
Security information leaks
Poor API design
Professional APIs always return a consistent JSON error structure.
2️⃣ What Does “Standardized Error Response” Mean?
Every error — regardless of type — returns:
{
"success": false,
"message": "Unauthorized",
"code": 401
}
Same structure for:
400
401
403
404
422
500
Frontend can now handle errors predictably.
3️⃣ Why Standardized Errors Are Important
✔ Frontend Simplicity
React/Next/Vue can always check:
if (!response.success) {
showToast(response.message)
}
✔ Security
Avoid exposing internal stack traces.
✔ Debugging
Consistent structure simplifies logging and monitoring.
✔ Enterprise Compliance
Professional APIs (Stripe, AWS, etc.) always standardize responses.
4️⃣ Step 1: Create a Standard Error Struct
Create file:
src/utils/error.rs
Add this struct:
use serde::Serialize;
#[derive(Serialize)]
pub struct JsonError {
pub success: bool,
pub message: String,
pub code: u16,
}
5️⃣ Step 2: Create Helper Functions for Common Errors
Add helper methods:
use actix_web::{HttpResponse, http::StatusCode};
pub fn error_response(status: StatusCode, message: &str) -> HttpResponse {
HttpResponse::build(status).json(JsonError {
success: false,
message: message.to_string(),
code: status.as_u16(),
})
}
Now we can use:
return Ok(error_response(StatusCode::UNAUTHORIZED, "Unauthorized"));
6️⃣ Step 3: Replace Raw Error Returns in Handlers
❌ Before (inconsistent):
Err(error::ErrorUnauthorized("Invalid credentials"))
✅ After (standardized):
return Ok(error_response(
StatusCode::UNAUTHORIZED,
"Invalid credentials",
));
Now every error has same structure.
7️⃣ Step 4: Handle 401 Unauthorized Properly
Example login failure:
if !valid {
return Ok(error_response(
StatusCode::UNAUTHORIZED,
"Invalid credentials",
));
}
Response:
{
"success": false,
"message": "Invalid credentials",
"code": 401
}
8️⃣ Step 5: Handle 403 Forbidden
Example role restriction:
if role != "admin" {
return Ok(error_response(
StatusCode::FORBIDDEN,
"Access denied",
));
}
9️⃣ Step 6: Handle 404 Not Found
if shop.is_none() {
return Ok(error_response(
StatusCode::NOT_FOUND,
"Shop not found",
));
}
🔟 Step 7: Handle Validation Errors (422)
return Ok(error_response(
StatusCode::UNPROCESSABLE_ENTITY,
"Invalid input data",
));
1️⃣1️⃣ Step 8: Handle Internal Server Errors
Never expose DB errors directly.
❌ Bad:
.map_err(error::ErrorInternalServerError)?
This may expose internal details.
✅ Better:
match db_call.await {
Ok(data) => data,
Err(_) => {
return Ok(error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"Something went wrong",
));
}
}
Hide internal details from users.
1️⃣2️⃣ Advanced: Global Error Handling Middleware
Instead of handling in every handler, you can use middleware to wrap errors.
Example:
use actix_web::middleware::ErrorHandlers;
This allows transforming all errors into your JsonError format.
1️⃣3️⃣ Standard Success Response (Optional Improvement)
You already use:
pub struct ApiResponse<T: Serialize> {
pub message: String,
pub data: T,
}
Upgrade it to:
#[derive(Serialize)]
pub struct ApiResponse<T: Serialize> {
pub success: bool,
pub message: String,
pub data: T,
}
Then always return:
Ok(HttpResponse::Ok().json(ApiResponse {
success: true,
message: "Shops fetched".to_string(),
data: shops,
}))
Now all responses (success + error) follow same pattern.
1️⃣4️⃣ Production Example: Final Structure
Success:
{
"success": true,
"message": "Shop created",
"data": { ... }
}
Error:
{
"success": false,
"message": "Unauthorized",
"code": 401
}
Perfectly consistent.
1️⃣5️⃣ Why This Prevents Security Leaks
Without standardization, you might accidentally expose:
SQL errors
Stack traces
Internal file paths
Sensitive logic
Standard wrapper prevents accidental leaks.
1️⃣6️⃣ Common Developer Mistakes
❌ Mixing raw text + JSON
❌ Returning HTML error pages
❌ Exposing DB error message
❌ Returning inconsistent structures
❌ Not setting correct HTTP status code
1️⃣7️⃣ Real Enterprise Pattern
Large SaaS systems always include:
error code
message
request id (optional)
timestamp (optional)
You can extend:
pub struct JsonError {
pub success: bool,
pub message: String,
pub code: u16,
pub timestamp: String,
}
1️⃣8️⃣ Add Timestamp (Optional Advanced)
use chrono::Utc;
timestamp: Utc::now().to_rfc3339(),
Now logs and client errors match timestamps.
1️⃣9️⃣ Add Request ID (Advanced Observability)
Generate request ID in middleware and attach to all responses.
This is enterprise monitoring practice.
2️⃣0️⃣ Final Architecture After Standardization
Your backend now has:
Structured success responses
Structured error responses
Proper HTTP status codes
No internal leaks
Clean frontend integration
Professional API design
✅ Summary
Standardized API error responses:
Improve frontend integration
Improve security
Improve debugging
Improve monitoring
Improve enterprise readiness
Top comments (0)