Flask Error Handling
When building web applications with Flask, things don't always go as planned. Users might request pages that don't exist, your code might encounter unexpected situations, or external services might fail. Error handling is the practice of gracefully managing these situations to improve both user experience and developer debugging.
In this guide, you'll learn how to implement effective error handling in your Flask applications.
Why Error Handling Matters
Without proper error handling:
- Users see cryptic error messages
- Security vulnerabilities might be exposed
- Debugging becomes challenging
- Your application looks unprofessional
Good error handling helps you:
- Present user-friendly error messages
- Log detailed information for debugging
- Maintain security by controlling what information is exposed
- Keep users informed about what went wrong
Flask's Default Error Handlers
Flask comes with some built-in error handling capabilities. By default, it will catch exceptions and display error pages, but they might not be what you want your users to see.
Let's look at the most common HTTP errors you'll want to handle:
- 404 Not Found: When a requested resource doesn't exist
- 400 Bad Request: When the server cannot process the request due to client error
- 500 Internal Server Error: When something goes wrong on the server
Custom Error Handlers in Flask
Flask allows you to register custom handlers for different HTTP error codes using the errorhandler
decorator.
Basic Error Handler Example
Here's how to create a basic custom 404 error handler:
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.route('/')
def home():
return 'Welcome to the homepage!'
if __name__ == '__main__':
app.run(debug=True)
In this example, when a user tries to access a page that doesn't exist, Flask will call your page_not_found
function and return your custom 404 template.
Creating Custom Error Templates
For the above example to work, you need to create a template file named 404.html
in your templates directory:
<!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;
}
</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>We couldn't find the page you were looking for.</p>
<p><a href="/">Return to homepage</a></p>
</body>
</html>
Handling Common HTTP Errors
Let's expand our error handling to cover more HTTP error codes:
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
@app.errorhandler(403)
def forbidden(e):
return render_template('403.html'), 403
@app.route('/')
def home():
return 'Welcome to the homepage!'
if __name__ == '__main__':
app.run(debug=True)
Handling Application Exceptions
Beyond HTTP errors, you can also handle Python exceptions directly:
from flask import Flask, render_template
app = Flask(__name__)
@app.errorhandler(ZeroDivisionError)
def handle_zero_division_error(e):
return render_template('error.html', error="Division by zero error"), 500
@app.route('/divide/<int:a>/<int:b>')
def divide(a, b):
return f"Result: {a / b}"
if __name__ == '__main__':
app.run(debug=True)
In this example, if someone accesses /divide/5/0
, they'll see your custom error page instead of a server error.
App-wide Error Handler
For a more comprehensive approach, you can create an application-wide error handler:
from flask import Flask, render_template, request
import traceback
import logging
app = Flask(__name__)
# Set up logging
logging.basicConfig(filename='app_errors.log', level=logging.ERROR)
@app.errorhandler(Exception)
def handle_exception(e):
# Log the error and stacktrace
app.logger.error(f"Unhandled exception: {str(e)}")
app.logger.error(traceback.format_exc())
# Return a custom error page
return render_template(
'error.html',
error_message="An unexpected error occurred",
error_details=str(e) if app.debug else ""
), 500
@app.route('/')
def home():
return 'Welcome to the homepage!'
@app.route('/error')
def cause_error():
# This will cause an error
x = 1 / 0
return "This will never execute"
if __name__ == '__main__':
app.run(debug=True)
With this setup, any unhandled exception in your application will be:
- Logged to
app_errors.log
- Displayed to the user via the
error.html
template - Only show detailed errors if the app is in debug mode
Error Handling with Blueprints
If your application uses Flask blueprints for organization, you can define error handlers for specific blueprints:
from flask import Flask, Blueprint, render_template
app = Flask(__name__)
admin = Blueprint('admin', __name__, url_prefix='/admin')
# Blueprint-specific error handler
@admin.errorhandler(403)
def admin_forbidden(e):
return render_template('admin/403.html'), 403
# Register the blueprint
app.register_blueprint(admin)
# App-wide error handler
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
if __name__ == '__main__':
app.run(debug=True)
In this example, 403 errors in the admin blueprint use a specific template, while other 404 errors use the global handler.
API Error Handling
When building APIs with Flask, you'll want to return JSON error responses instead of HTML:
from flask import Flask, jsonify
app = Flask(__name__)
@app.errorhandler(404)
def not_found(e):
return jsonify(error=str(e), code=404), 404
@app.errorhandler(500)
def server_error(e):
return jsonify(error="Internal server error", code=500), 500
@app.route('/api/user/<int:user_id>')
def get_user(user_id):
# Simulating a user not found situation
if user_id > 10:
# This will trigger the 404 error handler
return jsonify(error="User not found"), 404
return jsonify(id=user_id, name=f"User {user_id}")
if __name__ == '__main__':
app.run(debug=True)
Example output for a request to /api/user/20
:
{
"code": 404,
"error": "404 Not Found: The requested URL was not found on the server."
}
Creating a Custom Error Class
For more advanced applications, you might want to create a custom error class:
from flask import Flask, jsonify
app = Flask(__name__)
class APIError(Exception):
"""Base class for API errors"""
def __init__(self, message, status_code=400, payload=None):
self.message = message
self.status_code = status_code
self.payload = payload
super().__init__(self.message)
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
rv['status'] = self.status_code
return rv
@app.errorhandler(APIError)
def handle_api_error(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
@app.route('/api/resource/<id>')
def get_resource(id):
if id == "0":
raise APIError("Resource not available", status_code=404)
# Process the request normally
return jsonify({"id": id, "name": "Example Resource"})
if __name__ == '__main__':
app.run(debug=True)
Now when you access /api/resource/0
, you'll get a nicely formatted JSON error response:
{
"message": "Resource not available",
"status": 404
}
Best Practices for Error Handling
-
Be specific but secure: Provide enough information to help users, but don't leak sensitive details.
-
Maintain consistent error formats: Use the same structure for all error responses.
-
Log detailed information: Log detailed error information server-side for debugging.
-
Use appropriate HTTP status codes: Use the correct HTTP status code for each error type.
-
Provide recovery options: Give users a way to recover from errors (e.g., links to homepage).
-
Test your error handlers: Make sure your error handlers work correctly.
Real-World Example: E-commerce Application
Here's a more comprehensive example for an e-commerce application:
from flask import Flask, render_template, jsonify, request, g
import logging
from werkzeug.exceptions import HTTPException
import traceback
app = Flask(__name__)
# Configure logging
logging.basicConfig(
filename='ecommerce_errors.log',
level=logging.ERROR,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# HTTP Error handlers
@app.errorhandler(404)
def page_not_found(e):
if request.path.startswith('/api/'):
return jsonify(error="Resource not found", code=404), 404
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def server_error(e):
app.logger.error(f"500 error: {str(e)}")
app.logger.error(traceback.format_exc())
if request.path.startswith('/api/'):
return jsonify(error="Internal server error", code=500), 500
return render_template('errors/500.html'), 500
# Custom exception for business logic errors
class ProductError(Exception):
def __init__(self, message, code=400):
self.message = message
self.code = code
super().__init__(self.message)
@app.errorhandler(ProductError)
def handle_product_error(e):
if request.path.startswith('/api/'):
return jsonify(error=e.message, code=e.code), e.code
return render_template('errors/product_error.html', error=e.message), e.code
@app.route('/api/products/<product_id>')
def get_product(product_id):
if product_id == "out-of-stock":
raise ProductError("Product is out of stock", 400)
elif product_id == "not-found":
raise ProductError("Product does not exist", 404)
return jsonify({"id": product_id, "name": "Example Product"})
@app.route('/products/<product_id>')
def product_page(product_id):
if product_id == "out-of-stock":
raise ProductError("The requested product is currently out of stock.")
elif product_id == "not-found":
raise ProductError("We couldn't find the product you're looking for.", 404)
return render_template('product.html', product={"id": product_id, "name": "Example Product"})
# Catch-all exception handler
@app.errorhandler(Exception)
def handle_unexpected_error(e):
app.logger.error(f"Unhandled exception: {str(e)}")
app.logger.error(traceback.format_exc())
if request.path.startswith('/api/'):
return jsonify(error="An unexpected error occurred", code=500), 500
return render_template('errors/generic.html'), 500
if __name__ == '__main__':
app.run(debug=True)
This example handles:
- Regular HTTP errors (404, 500)
- API vs. Web interface errors differently
- Custom business logic exceptions
- Unexpected exceptions with proper logging
Summary
Proper error handling is an essential part of any Flask application. It improves user experience, makes debugging easier, and helps maintain the security of your application.
In this guide, you've learned:
- How to create custom error handlers for HTTP errors
- How to handle Python exceptions
- How to implement API error handling
- How to create custom error classes
- Best practices for Flask error handling
By implementing these techniques, your Flask applications will be more robust, maintainable, and user-friendly.
Additional Resources
Exercises
- Create a Flask application with custom error pages for 404, 403, and 500 errors.
- Build an API endpoint that handles various error conditions and returns appropriate JSON responses.
- Implement a logging system that records detailed error information while showing limited details to users.
- Create a custom exception class for handling form validation errors.
- Modify the e-commerce example to include database connection errors with appropriate error handling.
Remember that good error handling is as much about user experience as it is about technical correctness. Always consider how your error messages will be perceived by your users!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)