FastrAPI provides a comprehensive error handling system with built-in exceptions, automatic validation errors, and custom error handlers. Learn how to handle errors effectively to build robust APIs.
HTTPException
Raise HTTPException to return error responses with custom status codes and details:
from fastrapi import FastrAPI, HTTPException
app = FastrAPI()
@app.get ( "/users/ {user_id} " )
def get_user ( user_id : int ):
if user_id not in users_database:
raise HTTPException( status_code = 404 , detail = "User not found" )
return users_database[user_id]
if __name__ == "__main__" :
app.serve( "127.0.0.1" , 8080 )
The client receives:
{
"detail" : "User not found"
}
HTTPException automatically converts to a JSON response with the appropriate status code.
Common HTTP errors
Use standard HTTP status codes to indicate different error types:
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
418 I'm a teapot
422 Unprocessable Entity
@app.post ( "/items" )
def create_item ( name : str ):
if not name.strip():
raise HTTPException(
status_code = 400 ,
detail = "Item name cannot be empty"
)
return { "item" : name}
Custom error details
Provide structured error details for better client-side error handling:
from fastrapi import FastrAPI, HTTPException
app = FastrAPI()
@app.post ( "/charge" )
def process_payment ( amount : float , card_number : str ):
if amount <= 0 :
raise HTTPException(
status_code = 400 ,
detail = {
"error" : "invalid_amount" ,
"message" : "Amount must be greater than zero" ,
"field" : "amount" ,
"value" : amount
}
)
if not validate_card(card_number):
raise HTTPException(
status_code = 400 ,
detail = {
"error" : "invalid_card" ,
"message" : "Invalid card number format" ,
"field" : "card_number"
}
)
return { "status" : "success" , "transaction_id" : "txn_123" }
Response:
{
"detail" : {
"error" : "invalid_amount" ,
"message" : "Amount must be greater than zero" ,
"field" : "amount" ,
"value" : -10
}
}
Validation errors
FastrAPI automatically handles Pydantic validation errors and returns 422 responses:
from pydantic import BaseModel, Field
from fastrapi import FastrAPI
app = FastrAPI()
class Item ( BaseModel ):
name: str = Field( min_length = 3 )
price: float = Field( gt = 0 )
quantity: int = Field( ge = 0 )
@app.post ( "/items" )
def create_item ( item : Item):
return { "item" : item.dict()}
Invalid request:
{
"name" : "ab" ,
"price" : -10 ,
"quantity" : -5
}
Automatic error response:
{
"detail" : "Validation failed"
}
Validation errors are caught at the Rust layer for maximum performance. The validation happens before your Python handler executes.
Add custom headers to error responses:
from fastrapi import FastrAPI, HTTPException
app = FastrAPI()
@app.get ( "/rate-limited" )
def rate_limited_endpoint ():
if check_rate_limit_exceeded():
raise HTTPException(
status_code = 429 ,
detail = "Rate limit exceeded" ,
headers = {
"X-RateLimit-Limit" : "100" ,
"X-RateLimit-Remaining" : "0" ,
"X-RateLimit-Reset" : "1640000000" ,
"Retry-After" : "3600"
}
)
return { "data" : "success" }
The headers parameter accepts a dictionary of header names and values.
WebSocket exceptions
Use WebSocketException for WebSocket-specific errors:
from fastrapi import FastrAPI, WebSocketException
app = FastrAPI()
@app.websocket ( "/ws" )
async def websocket_endpoint ( ws ):
await ws.accept()
try :
data = await ws.receive_text()
if not validate_message(data):
raise WebSocketException(
code = 1003 ,
reason = "Invalid message format"
)
await ws.send_text( f "Received: { data } " )
except WebSocketException as e:
print ( f "WebSocket error: { e.code } - { e.reason } " )
await ws.close( code = e.code)
WebSocket close codes:
1000: Normal closure
1003: Unsupported data
1008: Policy violation
1011: Internal error
See RFC 6455 for complete list.
Error handling patterns
Try-except in handlers
Handle errors within your route handlers:
from fastrapi import FastrAPI, HTTPException
import logging
app = FastrAPI()
logger = logging.getLogger( __name__ )
@app.get ( "/users/ {user_id} " )
def get_user ( user_id : int ):
try :
user = database.fetch_user(user_id)
return user
except DatabaseConnectionError:
logger.error( "Database connection failed" )
raise HTTPException(
status_code = 503 ,
detail = "Service temporarily unavailable"
)
except Exception as e:
logger.exception( "Unexpected error" )
raise HTTPException(
status_code = 500 ,
detail = "Internal server error"
)
Dependency error handling
Handle errors in dependencies:
from fastrapi import FastrAPI, HTTPException, Depends
app = FastrAPI()
def verify_token ( token : str ):
if not token:
raise HTTPException(
status_code = 401 ,
detail = "Missing authentication token"
)
user = decode_token(token)
if not user:
raise HTTPException(
status_code = 401 ,
detail = "Invalid or expired token"
)
return user
@app.get ( "/protected" )
def protected_route ( user = Depends(verify_token)):
return { "message" : f "Hello, { user.name } " }
Centralized error responses
Create helper functions for consistent error responses:
from fastrapi import FastrAPI, HTTPException
from typing import Dict, Any
app = FastrAPI()
def not_found_error ( resource : str , id : Any) -> HTTPException:
return HTTPException(
status_code = 404 ,
detail = {
"error" : "not_found" ,
"message" : f " { resource } not found" ,
"resource" : resource,
"id" : id
}
)
def validation_error ( field : str , message : str ) -> HTTPException:
return HTTPException(
status_code = 422 ,
detail = {
"error" : "validation_error" ,
"field" : field,
"message" : message
}
)
@app.get ( "/users/ {user_id} " )
def get_user ( user_id : int ):
user = database.get(user_id)
if not user:
raise not_found_error( "User" , user_id)
return user
@app.post ( "/users" )
def create_user ( email : str , age : int ):
if age < 18 :
raise validation_error( "age" , "Must be 18 or older" )
# Create user
How error handling works
FastrAPI’s error handling is integrated at the Rust level for performance:
Python exception raised
When you raise HTTPException in Python, it’s caught by the Rust runtime.
Rust conversion
The exception is converted to an Axum response with the appropriate status code and JSON body (src/exceptions.rs:136-143).
Response sent
The error response is sent to the client immediately, bypassing further processing.
Status code reference
Use the status module for readable status codes:
from fastrapi import FastrAPI, HTTPException
from fastrapi import status
app = FastrAPI()
@app.get ( "/items/ {item_id} " )
def get_item ( item_id : int ):
if item_id not in database:
raise HTTPException(
status_code = status. HTTP_404_NOT_FOUND ,
detail = "Item not found"
)
return database[item_id]
Common status codes:
status.HTTP_200_OK
status.HTTP_201_CREATED
status.HTTP_400_BAD_REQUEST
status.HTTP_401_UNAUTHORIZED
status.HTTP_403_FORBIDDEN
status.HTTP_404_NOT_FOUND
status.HTTP_422_UNPROCESSABLE_ENTITY
status.HTTP_500_INTERNAL_SERVER_ERROR
See the API reference for the complete list.
Best practices
Use appropriate status codes
Choose the right HTTP status code for each error type:
400: Client error (bad input)
401: Authentication required
403: Authenticated but not authorized
404: Resource doesn’t exist
422: Validation failed
500: Server error
Provide helpful error messages
Write clear, actionable error messages: # Good
raise HTTPException(
status_code = 400 ,
detail = "Email format is invalid. Example: [email protected] "
)
# Bad
raise HTTPException( status_code = 400 , detail = "Bad request" )
Don't expose sensitive information
Always log errors server-side while keeping client errors simple: try :
result = complex_operation()
except Exception as e:
logger.exception( "Complex operation failed" ) # Server log
raise HTTPException(
status_code = 500 ,
detail = "Operation failed" # Client message
)
Use structured error responses
Return consistent error formats: {
"error" : "error_code" ,
"message" : "Human readable message" ,
"field" : "field_name" , # For validation errors
"timestamp" : "2024-01-01T12:00:00Z"
}
Pydantic validation Learn about automatic validation
Status codes Complete status code reference
API reference HTTPException API documentation
Request handling Understand request processing