Flask Error Handling
Production Flask applications handle errors gracefully. Instead of crashing with a stack trace or returning a blank page, a well-built app catches exceptions, logs them for debugging, and returns a meaningful response to the user. Flask provides several layers for managing errors.
The Two Types of Errors
| Type | Example | Flask Response |
|---|---|---|
| HTTP errors | 404 Not Found, 403 Forbidden | HTTPException raised by Flask or your code |
| Application errors | Database failure, division by zero | Python exception → 500 Internal Server Error |
Handling HTTP Errors
from flask import render_template
from werkzeug.exceptions import HTTPException
@app.errorhandler(404)
def not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(e):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def server_error(e):
return render_template('errors/500.html'), 500Catching All HTTP Errors in One Handler
@app.errorhandler(HTTPException)
def handle_exception(e):
return render_template(
'errors/generic.html',
code=e.code,
name=e.name,
description=e.description
), e.codeHandling Application Exceptions
Catch specific Python exceptions that your code might raise:
from sqlalchemy.exc import OperationalError
@app.errorhandler(OperationalError)
def database_error(e):
app.logger.error(f'Database error: {e}')
return render_template('errors/db_error.html'), 503Using Flask's Logger
Flask provides a built-in logger at app.logger. Log errors to a file so you can diagnose problems in production without exposing details to users:
import logging
from logging.handlers import RotatingFileHandler
if not app.debug:
handler = RotatingFileHandler(
'logs/app.log', maxBytes=1_000_000, backupCount=5
)
handler.setLevel(logging.ERROR)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
)
handler.setFormatter(formatter)
app.logger.addHandler(handler)Inside view functions, log errors with:
app.logger.error('Something failed: %s', str(e))
app.logger.warning('Suspicious request from %s', request.remote_addr)
app.logger.info('User %s logged in', username)Try-Except Inside View Functions
Wrap risky operations in try-except blocks to handle errors locally:
@app.route('/process')
def process():
try:
result = perform_complex_operation()
return jsonify({'result': result})
except ValueError as e:
return jsonify({'error': str(e)}), 400
except Exception as e:
app.logger.error('Unexpected error in /process: %s', str(e))
return jsonify({'error': 'An unexpected error occurred'}), 500API Error Responses
For APIs, return JSON error responses instead of HTML. Override Flask-RESTful's or Flask's default error format:
@app.errorhandler(404)
def api_not_found(e):
if request.path.startswith('/api/'):
return jsonify({'error': 'Resource not found', 'code': 404}), 404
return render_template('errors/404.html'), 404This returns JSON for API routes and HTML for browser routes — both from the same error handler.
Aborting with a Custom Message
from flask import abort
from werkzeug.exceptions import NotFound
# Simple abort
abort(404)
# Abort with a custom description
raise NotFound(description='The product ID you specified does not exist.')Error Handling Flow
Request arrives
│
View function runs
│
Exception raised?
├── HTTPException (404, 403, etc.)
│ │
│ @app.errorhandler(code) fires
│ │
│ Returns custom error page/JSON
│
└── Python Exception (ValueError, etc.)
│
@app.errorhandler(ExceptionClass) fires (if registered)
│
OR Flask catches it → 500 error handler → logs it
Summary
Register error handlers with @app.errorhandler() for specific HTTP codes and Python exception types. Log errors using app.logger with a rotating file handler so you can diagnose production issues without revealing internals to users. Return HTML for browser-facing errors and JSON for API errors. Wrap risky operations in try-except blocks and use abort() to trigger error responses from inside view functions.
