Ojasa Mirai

Ojasa Mirai

FastAPI

Loading...

Learning Level

🟢 Beginner🔵 Advanced
🚀 Error Basics📚 HTTP Exceptions📚 Custom Exceptions📚 Error Responses📚 Exception Handlers📚 Validation Errors📚 Error Logging📚 Error Best Practices
Fastapi/Error Handling/Error Practices

Error Practices

Learn the fundamentals of error practices in FastAPI.

🎯 Core Concept

Error handling best practices ensure your API is reliable, maintainable, and provides excellent debugging information. Following established patterns helps you handle edge cases consistently and build robust systems.

📖 What You'll Learn

In this section, you'll understand:

  • Best practices for error handling
  • How it applies to real-world API development
  • Designing error handling strategies
  • Common patterns and anti-patterns

💡 Comprehensive Error Handling Example

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
import logging

logger = logging.getLogger(__name__)
app = FastAPI()

class ValidationResult:
    def __init__(self, is_valid: bool, errors: list = None):
        self.is_valid = is_valid
        self.errors = errors or []

class UserCreate(BaseModel):
    username: str
    email: str
    age: int

def validate_user(user: UserCreate) -> ValidationResult:
    errors = []

    if len(user.username) < 3:
        errors.append("Username must be at least 3 characters")

    if "@" not in user.email:
        errors.append("Invalid email format")

    if user.age < 18:
        errors.append("Must be at least 18 years old")

    return ValidationResult(len(errors) == 0, errors)

@app.post("/users/", status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
    # Validate early
    validation = validate_user(user)
    if not validation.is_valid:
        logger.warning(f"User validation failed for {user.username}")
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail={"errors": validation.errors}
        )

    # Check for duplicates
    if db.user_exists(user.email):
        logger.warning(f"User already exists: {user.email}")
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User with this email already exists"
        )

    try:
        # Create user
        new_user = db.create_user(user)
        logger.info(f"User created: {new_user.id}")
        return new_user

    except Exception as e:
        logger.error(f"Failed to create user {user.email}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to create user"
        )

🔍 Error Handling Anti-Patterns to Avoid

❌ DON'T: Expose implementation details

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    try:
        return db.query("SELECT * FROM items WHERE id = ?", item_id)
    except Exception as e:
        return JSONResponse(status_code=500, content={"detail": str(e)})

✅ DO: Return generic messages in production

@app.get("/items/{item_id}")
async def get_item(item_id: int):
    try:
        return db.query("SELECT * FROM items WHERE id = ?", item_id)
    except Exception as e:
        logger.error(f"Database error: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Internal server error"
        )

💼 Complete Error Handling Strategy

from enum import Enum
from typing import Optional

class ErrorSeverity(str, Enum):
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"

class AppError(Exception):
    def __init__(
        self,
        code: str,
        message: str,
        status_code: int,
        severity: ErrorSeverity = ErrorSeverity.ERROR,
        user_message: Optional[str] = None
    ):
        self.code = code
        self.message = message
        self.status_code = status_code
        self.severity = severity
        self.user_message = user_message or message

# Specific exceptions
class ResourceNotFoundError(AppError):
    def __init__(self, resource: str, resource_id):
        super().__init__(
            code="NOT_FOUND",
            message=f"{resource} {resource_id} not found",
            status_code=404,
            user_message=f"{resource} not found"
        )

class InvalidInputError(AppError):
    def __init__(self, field: str, reason: str):
        super().__init__(
            code="INVALID_INPUT",
            message=f"Invalid value for field '{field}': {reason}",
            status_code=422,
            user_message=f"Invalid {field}: {reason}"
        )

class AuthenticationError(AppError):
    def __init__(self):
        super().__init__(
            code="UNAUTHORIZED",
            message="Authentication required",
            status_code=401,
            user_message="Please provide valid credentials"
        )

@app.exception_handler(AppError)
async def app_error_handler(request, exc):
    logger.log(
        logging.getLevelName(exc.severity),
        f"{exc.code}: {exc.message}"
    )

    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": exc.code,
            "message": exc.user_message,
            "timestamp": datetime.utcnow().isoformat()
        }
    )

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    if user_id < 1:
        raise InvalidInputError("user_id", "Must be positive")

    user = db.get_user(user_id)
    if not user:
        raise ResourceNotFoundError("User", user_id)

    return user

How it applies to real-world API development

Proper error practices enable:

  • **Reliability**: Graceful degradation instead of crashes
  • **Debuggability**: Clear error messages and logs
  • **User experience**: Helpful feedback instead of confusing errors
  • **Monitoring**: Track error patterns to fix systemic issues

Example - Financial transaction API:

class InsufficientFundsError(AppError):
    def __init__(self, required: float, available: float):
        super().__init__(
            code="INSUFFICIENT_FUNDS",
            message=f"Insufficient funds. Required: {required}, Available: {available}",
            status_code=402,
            severity=ErrorSeverity.WARNING,
            user_message="Your account has insufficient funds"
        )

@app.post("/transfer")
async def transfer(transfer: TransferRequest):
    # Validate early
    if transfer.amount <= 0:
        raise InvalidInputError("amount", "Must be greater than 0")

    # Check funds
    account = db.get_account(transfer.from_id)
    if account.balance < transfer.amount:
        raise InsufficientFundsError(transfer.amount, account.balance)

    try:
        # Execute transfer
        db.transfer(transfer.from_id, transfer.to_id, transfer.amount)
        logger.info(f"Transfer successful: {transfer.amount}")
        return {"status": "success"}
    except Exception as e:
        logger.error(f"Transfer failed: {str(e)}", exc_info=True)
        raise AppError(
            code="TRANSFER_FAILED",
            message="Transfer could not be completed",
            status_code=500,
            user_message="Transfer failed. Please try again."
        )

Common patterns and best practices

  • ✅ Validate input early and fail fast
  • ✅ Use specific exception types for different errors
  • ✅ Provide user-friendly error messages
  • ✅ Log with sufficient context for debugging
  • ✅ Don't expose sensitive implementation details
  • ✅ Use appropriate HTTP status codes
  • ✅ Handle errors at different levels (validation, business logic, infrastructure)
  • ✅ Test error scenarios thoroughly
  • ✅ Monitor errors and set up alerts for critical issues
  • ✅ Document error codes and their meanings

🔑 Key Takeaways

  • ✅ Understand the purpose of error practices
  • ✅ Know when to apply this pattern
  • ✅ Recognize its benefits in real-world scenarios
  • ✅ Be prepared to use it in your projects

Ready to explore more? Check out the advanced section for production patterns and edge cases.


Resources

Python Docs

Ojasa Mirai

Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.

Learn Deep • Build Real • Verify Skills • Launch Forward

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn