Debug School

rakesh kumar
rakesh kumar

Posted on

Standardize API Error Responses in Actix-Web (Rust)

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
Enter fullscreen mode Exit fullscreen mode

This creates:

Inconsistent frontend handling

Hard debugging

Security information leaks

Poor API design
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Same structure for:

400

401

403

404

422

500
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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(),
    })
}
Enter fullscreen mode Exit fullscreen mode

Now we can use:

return Ok(error_response(StatusCode::UNAUTHORIZED, "Unauthorized"));
Enter fullscreen mode Exit fullscreen mode

6️⃣ Step 3: Replace Raw Error Returns in Handlers
Before (inconsistent):

Err(error::ErrorUnauthorized("Invalid credentials"))
Enter fullscreen mode Exit fullscreen mode

✅ After (standardized):

return Ok(error_response(
    StatusCode::UNAUTHORIZED,
    "Invalid credentials",
));
Enter fullscreen mode Exit fullscreen mode

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",
    ));
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "success": false,
  "message": "Invalid credentials",
  "code": 401
}
Enter fullscreen mode Exit fullscreen mode

8️⃣ Step 5: Handle 403 Forbidden

Example role restriction:

if role != "admin" {
    return Ok(error_response(
        StatusCode::FORBIDDEN,
        "Access denied",
    ));
}
Enter fullscreen mode Exit fullscreen mode

9️⃣ Step 6: Handle 404 Not Found

if shop.is_none() {
    return Ok(error_response(
        StatusCode::NOT_FOUND,
        "Shop not found",
    ));
}
Enter fullscreen mode Exit fullscreen mode

🔟 Step 7: Handle Validation Errors (422)

return Ok(error_response(
    StatusCode::UNPROCESSABLE_ENTITY,
    "Invalid input data",
));
Enter fullscreen mode Exit fullscreen mode

1️⃣1️⃣ Step 8: Handle Internal Server Errors

Never expose DB errors directly.

Bad:

.map_err(error::ErrorInternalServerError)?
Enter fullscreen mode Exit fullscreen mode

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",
        ));
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

Then always return:

Ok(HttpResponse::Ok().json(ApiResponse {
    success: true,
    message: "Shops fetched".to_string(),
    data: shops,
}))
Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

You can extend:

pub struct JsonError {
    pub success: bool,
    pub message: String,
    pub code: u16,
    pub timestamp: String,
}
Enter fullscreen mode Exit fullscreen mode

1️⃣8️⃣ Add Timestamp (Optional Advanced)

use chrono::Utc;

timestamp: Utc::now().to_rfc3339(),
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Summary

Standardized API error responses:

Improve frontend integration

Improve security

Improve debugging

Improve monitoring

Improve enterprise readiness
Enter fullscreen mode Exit fullscreen mode

Top comments (0)