Skip to main content

Flask Error Handling

Introduction

Error handling is a critical aspect of web application development. In any web application, things can go wrong - users might request non-existent pages, server-side processing might fail, or external services might be unavailable. Flask provides several mechanisms to handle these errors gracefully, improving user experience and making debugging easier.

In this guide, we'll learn how to:

  • Handle common HTTP errors like 404 and 500
  • Create custom error pages
  • Work with application-wide error handlers
  • Handle exceptions in Flask routes
  • Implement logging for errors

By the end of this guide, you'll be able to implement robust error handling in your Flask applications.

HTTP Error Codes Basics

Before diving into Flask's error handling, let's understand the most common HTTP error codes you'll encounter:

  • 400 Bad Request: The server cannot process the request due to client error
  • 401 Unauthorized: Authentication is required
  • 403 Forbidden: Server understood the request but refuses to authorize it
  • 404 Not Found: Resource not found
  • 500 Internal Server Error: Generic server error message
  • 503 Service Unavailable: The server is temporarily unable to handle the request

Basic Error Handling in Flask

Default Error Behavior

By default, Flask returns a basic HTML page when an error occurs. For example, when a user requests a non-existent page, Flask returns a 404 error with minimal styling.

Let's look at a simple Flask application:

python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
return "Welcome to the homepage!"

if __name__ == '__main__':
app.run(debug=True)

If a user visits /non-existent-page, Flask will return its default 404 page.

Custom Error Handlers

We can customize how errors are displayed by registering error handler functions using the app.errorhandler decorator.

Here's how to create a custom 404 error page:

python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
return "Welcome to the homepage!"

@app.errorhandler(404)
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404

if __name__ == '__main__':
app.run(debug=True)

For this to work, you need to create a 404.html template in your templates folder:

html
<!-- templates/404.html -->
<!DOCTYPE html>
<html>
<head>
<title>Page Not Found</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding-top: 50px;
}
h1 {
color: #d9534f;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>404 - Page Not Found</h1>
<p>The page you are looking for doesn't exist.</p>
<p><a href="/">Go back to homepage</a></p>
</div>
</body>
</html>

Handling Multiple Error Codes

You can register handlers for different HTTP error codes:

python
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404

@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500

Handling Exceptions

Try-Except in Route Functions

You can use Python's try-except blocks to catch exceptions within your route functions:

python
@app.route('/divide/<int:num1>/<int:num2>')
def divide(num1, num2):
try:
result = num1 / num2
return f"The result is: {result}"
except ZeroDivisionError:
return "Error: Cannot divide by zero!", 400

In this example:

  • If the user visits /divide/10/2, they'll see "The result is: 5.0"
  • If the user visits /divide/10/0, they'll see "Error: Cannot divide by zero!"

Application-Wide Exception Handling

Flask allows you to register handlers for specific Python exceptions across your entire application:

python
@app.errorhandler(ZeroDivisionError)
def handle_zero_division_error(e):
return "Zero division error occurred", 400

@app.errorhandler(ValueError)
def handle_value_error(e):
return f"Invalid value: {str(e)}", 400

Now any uncaught ZeroDivisionError or ValueError in your application will be handled by these functions.

Advanced Error Handling

Custom Error Classes

You can create custom exception classes for your application:

python
class InvalidAPIUsage(Exception):
status_code = 400

def __init__(self, message, status_code=None, payload=None):
super().__init__()
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload

def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
return rv

@app.errorhandler(InvalidAPIUsage)
def handle_invalid_api_usage(e):
response = jsonify(e.to_dict())
response.status_code = e.status_code
return response

@app.route('/api/item/<int:item_id>')
def get_item(item_id):
if item_id <= 0:
raise InvalidAPIUsage('Item ID must be positive', status_code=400)
# Process the request...
return f"Item ID: {item_id}"

Logging Errors

It's important to log errors for debugging. Flask integrates with Python's logging module:

python
import logging
from flask import Flask

app = Flask(__name__)

# Configure logging
handler = logging.FileHandler('app.log')
handler.setLevel(logging.ERROR)
app.logger.addHandler(handler)

@app.errorhandler(500)
def internal_server_error(e):
app.logger.error(f'500 error: {str(e)}')
return render_template('500.html'), 500

@app.route('/error')
def trigger_error():
try:
# Simulate an error
1 / 0
except Exception as e:
app.logger.exception('An error occurred')
return f"An error occurred: {str(e)}", 500

With this setup, errors will be logged to app.log for later review.

Real-World Example: API Error Handling

Let's create a more comprehensive example of error handling in a simple API:

python
from flask import Flask, jsonify, request

app = Flask(__name__)

# Database simulation
users = {
1: {"name": "Alice", "email": "[email protected]"},
2: {"name": "Bob", "email": "[email protected]"}
}

class APIError(Exception):
"""Base class for API errors"""
def __init__(self, message, status_code):
self.message = message
self.status_code = status_code

@app.errorhandler(APIError)
def handle_api_error(error):
response = jsonify({"error": error.message})
response.status_code = error.status_code
return response

@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "Resource not found"}), 404

@app.errorhandler(400)
def bad_request(e):
return jsonify({"error": "Bad request"}), 400

@app.errorhandler(500)
def server_error(e):
app.logger.error(f"Server error: {str(e)}")
return jsonify({"error": "Internal server error"}), 500

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
try:
if user_id not in users:
raise APIError("User not found", 404)
return jsonify(users[user_id])
except ValueError:
raise APIError("Invalid user ID", 400)

@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()

if not data:
raise APIError("No data provided", 400)

if 'name' not in data or 'email' not in data:
raise APIError("Missing required fields", 400)

# In a real app, you would insert into a database
new_id = max(users.keys()) + 1
users[new_id] = {"name": data['name'], "email": data['email']}

return jsonify({"id": new_id, "name": data['name'], "email": data['email']}), 201

if __name__ == '__main__':
app.run(debug=True)

In this example:

  1. We defined a custom APIError class for API-specific errors
  2. We registered error handlers for our custom exception and common HTTP error codes
  3. We raised appropriate exceptions based on different error conditions
  4. We returned proper JSON responses with appropriate status codes

Blueprint Error Handling

If your app is structured using Flask blueprints, you can register error handlers for specific blueprints:

python
from flask import Flask, Blueprint, render_template

app = Flask(__name__)
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')

@admin_bp.errorhandler(404)
def admin_not_found(e):
return render_template('admin/404.html'), 404

# Register the blueprint
app.register_blueprint(admin_bp)

# App-wide error handler
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404

In this example, 404 errors occurring within the admin blueprint will use the admin_not_found handler, while other 404 errors will use the app-wide handler.

Best Practices for Error Handling

  1. Be specific: Create custom error pages for common errors like 404 and 500
  2. Don't expose sensitive information: In production, don't show stack traces or detailed error messages to users
  3. Log errors: Always log errors on the server for debugging
  4. Return appropriate status codes: Use the correct HTTP status codes for different error conditions
  5. Provide helpful messages: Give users guidance on what went wrong and what they can do next
  6. Consider the format: Return errors in the same format as the rest of your application (HTML for websites, JSON for APIs)

Summary

Error handling is a crucial part of building robust Flask applications. In this guide, we've learned:

  • How to create custom error pages for different HTTP error codes
  • Techniques for handling exceptions in route functions
  • How to implement application-wide error handlers
  • Ways to log errors for debugging
  • How to structure error handling for APIs
  • Blueprint-specific error handling

By implementing these patterns, you'll create applications that are more user-friendly and easier to debug when things go wrong.

Additional Resources

Exercises

  1. Create a Flask application with custom error pages for 404, 403, and 500 errors.
  2. Build a simple API that validates input and returns appropriate error responses.
  3. Implement logging for your Flask application that records errors to a file.
  4. Create a custom exception class and error handler for handling form validation errors.
  5. Modify the real-world API example to include authentication and proper error handling for unauthorized access.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)