← Back to Blog

Error Handling in Rust: Building Reliable Backend APIs

Every backend system encounters failure at some point — a database goes down, a user enters the wrong password, or requested data simply doesn't exist. What separates a fragile API from a production-grade one is not the absence of errors, but how predictably those errors are handled.

Rust treats failure as a first-class part of program design, giving backend teams the tools to build APIs that stay reliable, secure, and easy to debug — even when things go wrong.

Predictable, Not Perfect

Good error handling doesn't mean hiding every error. It means responding to each failure in a predictable way while keeping enough detail in logs for developers to troubleshoot safely.

What is Error Handling and Why Do We Use It?

Error handling is how an application detects, manages, and responds to failures. In backend APIs, failures can come from invalid credentials, missing data, database problems, or unexpected system issues.

We rely on error handling to:

A Simple Example

If a login fails because the password is incorrect, the API should return 401 Unauthorized — not crash or expose internal details.

A Real-Life Analogy: The Restaurant Waiter

Think about a restaurant. A customer asks a waiter for a dish. The waiter checks with the kitchen, and several outcomes are possible:

  • The dish is available → the waiter brings it to the customer
  • The dish is not available → the waiter tells the customer it's unavailable
  • The kitchen is temporarily closed → the waiter explains the order can't be completed right now
  • The waiter can't understand the order → the waiter asks for clarification

The waiter never simply stops working or exposes internal kitchen details — each situation is handled differently. An API works the same way:

Client → API → Business Logic / Database → Response

The API receives a request, performs the required operation, handles possible failures, and returns an appropriate response.

How Rust Handles Errors

Rust mainly uses Result<T, E> when an operation can succeed or fail, and Option<T> when a value may or may not exist.

The ? operator makes error propagation simple — if an operation succeeds, execution continues; if it fails, the error is returned to the caller. This makes failure paths explicit in the code instead of relying on hidden exceptions.

Real-Life API Example: Login

Consider a login API. The client sends a username and password, and the server checks the database and verifies the password. Possible results include:

Situation Response
Correct username and password200 OK
Wrong password401 Unauthorized
User does not exist404 Not Found
Account is inactive403 Forbidden
Database failure500 Internal Server Error

Each result represents a different situation — just like the different situations handled by the restaurant waiter.

Custom Errors in Actix Web

Instead of returning raw database or library errors, a backend can define application-level errors such as:

  • InvalidCredentials
  • UserNotFound
  • UserInactive
  • DatabaseError

Actix Web's ResponseError can convert these errors into appropriate HTTP responses. This keeps business logic separate from HTTP response handling and makes API behavior consistent.

Logging and PII Protection

Error handling and logging work together. Logs help developers understand what happened without exposing sensitive information. Common levels are:

Log Levels

DEBUG — detailed information useful during development
INFO — normal application events
WARN — expected but unsuccessful operations
ERROR — actual application or infrastructure failures

For example, a failed password attempt is normally a WARN, while a database connection failure is an ERROR.

PII (Personally Identifiable Information) and security-sensitive data should never be exposed in logs — passwords, password hashes, authentication tokens, API keys, and unnecessary personal information should always stay out of them.

Bad vs Good Logging

Avoid: Login failed username=priyanshu password=password123
Prefer: Login failed username=p***u

The team still gets useful troubleshooting information without exposing the password or full username.

Why Error Levels Matter: The Restaurant Analogy, Revisited

Log Level Restaurant Equivalent
DEBUGThe waiter checks the kitchen for the requested dish
INFOThe order was successfully placed
WARNThe requested dish is unavailable
ERRORThe kitchen system is completely down

Backend logs should communicate the severity of an event rather than treating every message as an error.

Testing Error Handling

Error handling should be tested at two levels:

01 — Unit Tests

Verify individual pieces such as password verification, PII masking, and error-to-status-code mapping.

02 — Functional Tests

Verify the complete API flow, such as successful login, wrong password, missing user, inactive account, and database failure.

Testing both levels helps ensure that the error is handled correctly internally and also produces the expected API response.

Best Practices

  • Use Result for operations that can fail
  • Use Option when a value may not exist
  • Use ? for clean error propagation
  • Create meaningful application errors
  • Return appropriate HTTP status codes
  • Keep internal implementation details out of API responses
  • Use DEBUG, INFO, WARN, and ERROR appropriately
  • Never log passwords, password hashes, tokens, or unnecessary PII
  • Test both success and failure scenarios

Final Thoughts

Error handling is not only a technical concept — it's about deciding what should happen when something doesn't go as planned. Just like a good restaurant waiter handles different customer and kitchen situations gracefully, a well-designed API should handle different failures predictably.

Built for Reliability

Rust's Result, Option, custom errors, Actix Web error handling, secure logging, and thorough testing together provide a strong foundation for building reliable backend systems.

← Back to All Articles