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

TypeExampleFlask Response
HTTP errors404 Not Found, 403 ForbiddenHTTPException raised by Flask or your code
Application errorsDatabase failure, division by zeroPython 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'), 500

Catching 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.code

Handling 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'), 503

Using 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'}), 500

API 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'), 404

This 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.

Leave a Comment

Your email address will not be published. Required fields are marked *