error-handling

Consistent error handling patterns for career_ios_backend FastAPI application.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "error-handling" with this command: npx skills add youngger9765/career_ios_backend/youngger9765-career-ios-backend-error-handling

Error Handling Skill

Purpose

Consistent error handling patterns for career_ios_backend FastAPI application.

Auto-Activation

Triggers on:

  • ✅ "error", "exception", "validation"

  • ✅ "錯誤處理", "異常處理"

  • ✅ "error handling", "exception handling"

Core Principles (Prototype Phase)

Keep it simple:

  • ✅ Use FastAPI's HTTPException

  • ✅ Return clear error messages

  • ✅ Log errors appropriately

  • ❌ Don't over-engineer custom exceptions (yet)

Standard Error Response Format

FastAPI HTTPException Pattern

from fastapi import HTTPException, status

404 Not Found

raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Client not found" )

400 Bad Request

raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid client code format" )

401 Unauthorized

raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials", headers={"WWW-Authenticate": "Bearer"} )

403 Forbidden

raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions" )

500 Internal Server Error

raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Database connection failed" )

HTTP Status Codes (Quick Reference)

Code When to Use Example

200 Success GET resource

201 Created POST new resource

204 No Content DELETE successful

400 Bad Request Invalid input

401 Unauthorized Missing/invalid token

403 Forbidden Insufficient permissions

404 Not Found Resource doesn't exist

409 Conflict Duplicate resource

422 Validation Error Pydantic validation fails

500 Server Error Unexpected error

Common Patterns

Pattern 1: Resource Not Found

from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session

@router.get("/clients/{client_id}") async def get_client( client_id: int, db: Session = Depends(get_db) ): client = db.query(Client).filter(Client.id == client_id).first()

if not client:
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail=f"Client with id {client_id} not found"
    )

return client

Pattern 2: Validation Error

@router.post("/clients", status_code=status.HTTP_201_CREATED) async def create_client( client_data: ClientCreate, db: Session = Depends(get_db) ): # Check for duplicate existing = db.query(Client).filter( Client.email == client_data.email ).first()

if existing:
    raise HTTPException(
        status_code=status.HTTP_400_BAD_REQUEST,
        detail=f"Client with email {client_data.email} already exists"
    )

# Create client
new_client = Client(**client_data.dict())
db.add(new_client)
db.commit()
db.refresh(new_client)

return new_client

Pattern 3: Database Error

from sqlalchemy.exc import SQLAlchemyError import logging

logger = logging.getLogger(name)

@router.post("/clients") async def create_client( client_data: ClientCreate, db: Session = Depends(get_db) ): try: new_client = Client(**client_data.dict()) db.add(new_client) db.commit() db.refresh(new_client) return new_client

except SQLAlchemyError as e:
    db.rollback()
    logger.error(f"Database error creating client: {str(e)}", exc_info=True)
    raise HTTPException(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        detail="Failed to create client"
    )

Pattern 4: Authentication Error

from app.core.security import verify_password

@router.post("/auth/login") async def login( credentials: LoginRequest, db: Session = Depends(get_db) ): user = db.query(User).filter(User.username == credentials.username).first()

if not user or not verify_password(credentials.password, user.hashed_password):
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Incorrect username or password",
        headers={"WWW-Authenticate": "Bearer"}
    )

# Generate token
access_token = create_access_token(data={"sub": user.username})
return {"access_token": access_token, "token_type": "bearer"}

Pydantic Validation (Automatic)

FastAPI automatically validates request bodies using Pydantic:

from pydantic import BaseModel, EmailStr, validator

class ClientCreate(BaseModel): name: str email: EmailStr # Automatic email validation age: int

@validator('age')
def age_must_be_positive(cls, v):
    if v < 0:
        raise ValueError('Age must be positive')
    return v

@validator('name')
def name_must_not_be_empty(cls, v):
    if not v.strip():
        raise ValueError('Name cannot be empty')
    return v

Automatic 422 Response: When validation fails, FastAPI returns:

{ "detail": [ { "loc": ["body", "email"], "msg": "value is not a valid email address", "type": "value_error.email" } ] }

Logging Best Practices

Basic Logging Setup

import logging

At the top of your module

logger = logging.getLogger(name)

Log levels

logger.debug("Detailed debug info") # Development only logger.info("General information") # Normal operations logger.warning("Warning message") # Potential issues logger.error("Error occurred") # Error happened logger.critical("Critical failure") # System failure

Log with Context

@router.post("/clients") async def create_client(client_data: ClientCreate, db: Session = Depends(get_db)): logger.info(f"Creating client: {client_data.name}")

try:
    # ... create client
    logger.info(f"Client created successfully: id={new_client.id}")
    return new_client

except Exception as e:
    logger.error(
        f"Failed to create client: {client_data.name}",
        exc_info=True  # Include full traceback
    )
    raise

What to Log

✅ Do Log:

  • Incoming requests (at INFO level)

  • Successful operations (at INFO level)

  • Errors with context (at ERROR level)

  • Authentication failures (at WARNING level)

❌ Don't Log:

  • Passwords or tokens

  • Sensitive user data

  • Too much detail in production (use DEBUG level in development)

Error Handling Checklist

Before Commit

  • All error cases return appropriate HTTP status codes

  • Error messages are clear and actionable

  • Sensitive data not exposed in error messages

  • Errors are logged with sufficient context

  • Database transactions rolled back on error

  • Tests cover error scenarios

Testing Error Cases

import pytest from httpx import AsyncClient from app.main import app

@pytest.mark.asyncio async def test_client_not_found(): """Test 404 when client doesn't exist""" async with AsyncClient(app=app, base_url="http://test") as client: response = await client.get("/api/v1/clients/99999")

assert response.status_code == 404
assert "not found" in response.json()["detail"].lower()

@pytest.mark.asyncio async def test_duplicate_client(auth_headers): """Test 400 when creating duplicate client""" client_data = { "name": "Test Client", "email": "test@example.com" }

async with AsyncClient(app=app, base_url="http://test") as client:
    # Create first time
    response1 = await client.post(
        "/api/v1/clients",
        headers=auth_headers,
        json=client_data
    )
    assert response1.status_code == 201

    # Try to create duplicate
    response2 = await client.post(
        "/api/v1/clients",
        headers=auth_headers,
        json=client_data
    )
    assert response2.status_code == 400
    assert "already exists" in response2.json()["detail"].lower()

@pytest.mark.asyncio async def test_invalid_token(): """Test 401 with invalid token""" async with AsyncClient(app=app, base_url="http://test") as client: response = await client.get( "/api/v1/clients", headers={"Authorization": "Bearer invalid_token"} )

assert response.status_code == 401

Quick Reference Table

Scenario Status Code Pattern

Resource not found 404 Check if not resource: raise HTTPException(404)

Invalid input 400 Validate before processing

Duplicate resource 400 Check existence first

Unauthorized 401 Verify token/credentials

Forbidden 403 Check permissions

Validation error 422 Use Pydantic validators

Database error 500 Try-except with rollback

Anti-Patterns to Avoid

❌ Generic Error Messages

Bad

raise HTTPException(status_code=400, detail="Bad request")

Good

raise HTTPException( status_code=400, detail="Client email format is invalid" )

❌ Exposing Internal Details

Bad - exposes database structure

raise HTTPException( status_code=500, detail=f"SQLAlchemy error: {str(e)}" )

Good - user-friendly message

logger.error(f"Database error: {str(e)}", exc_info=True) raise HTTPException( status_code=500, detail="Failed to process request" )

❌ Swallowing Exceptions

Bad - silently fails

try: db.commit() except Exception: pass # Error ignored!

Good - handle or re-raise

try: db.commit() except Exception as e: logger.error(f"Commit failed: {e}", exc_info=True) db.rollback() raise HTTPException(500, detail="Operation failed")

Related Skills

  • api-development: API design patterns

  • debugging: Debug error scenarios

  • quality-standards: Code quality requirements

Skill Version: v1.0 Last Updated: 2025-12-25 Project: career_ios_backend (Prototype Phase)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

third-party-apis

No summary provided by upstream source.

Repository SourceNeeds Review
General

debugging

No summary provided by upstream source.

Repository SourceNeeds Review
General

requirements-clarification

No summary provided by upstream source.

Repository SourceNeeds Review
General

quality-standards

No summary provided by upstream source.

Repository SourceNeeds Review