Everyone Can Learn Program
中文 Start learning
Specialty · BackendLesson 5 of 6Free to read

Middleware and errors · Backend Development

How do you add logging and error handling to every route without repeating yourself?

Course contents

Why you need it

Every handler needs the same extras: log the request, time it, turn crashes into a clean 500, check the login. Copying that into fifty handlers is how bugs and gaps appear. Middleware wraps handlers once, and every framework has it: decorators in Flask and FastAPI, app.use in Express, filters in Spring.

The intuition

A decorator is a function that takes a handler and returns a new handler that does something before and after calling the original:

def logged(handler):
    def wrapper(request):
        print("->", request["path"])
        response = handler(request)
        print("<-", response[0])
        return response
    return wrapper

@logged
def home(request):
    return 200, "welcome"

@logged above def home means home = logged(home). Calling home(...) now runs wrapper, which calls the original.

Stack them and they nest like layers of an onion: with @a above @b, a request goes through a, then b, then the handler, and the response comes back out through b, then a.

An error middleware catches any exception, logs the details for you, and sends the client a plain 500 {"error": "internal error"}. Never send the exception text or a stack trace to the client: it can reveal file paths, SQL and secrets.

Definition

Middleware

Code that runs around every handler: logging, timing, auth, error handling.

Decorator

A function that takes a function and returns a wrapped version. @name above def applies it.

Examples

A logging decorator

Try it yourself

The wrapper runs around the handler.

def logged(handler):
    def wrapper(request):
        print("->", request["path"])
        response = handler(request)
        print("<-", response[0])
        return response
    return wrapper

@logged
def home(request):
    return 200, "welcome"

result = home({"path": "/"})
print(result)
Output
-> /
<- 200
(200, 'welcome')
Step by step
  1. L9Same as writing home = logged(home) after the def.
  2. L13This calls wrapper, not the original home.
  3. L3Before the handler.-> /
  4. L4The original handler runs here.
  5. L5After the handler.<- 200

Layers of an onion

Try it yourself

Two decorators: in through the outer one, out through it last.

def layer(name):
    def decorate(handler):
        def wrapper(request):
            print("in", name)
            response = handler(request)
            print("out", name)
            return response
        return wrapper
    return decorate

@layer("A")
@layer("B")
def hello(request):
    print("handler")
    return 200, "hi"

hello({})
Output
in A
in B
handler
out B
out A
Step by step
  1. L1A decorator factory: layer("A") returns a decorator.
  2. L11Applied last, so it's the outermost layer.
  3. L17in A, in B, handler, out B, out A.

Errors become a clean 500

Try it yourself

The client gets a plain message; the details go to your log.

LOG = []

def catch_errors(handler):
    def wrapper(request):
        try:
            return handler(request)
        except Exception as e:
            LOG.append(f"{request['path']}: {type(e).__name__}: {e}")
            return 500, {"error": "internal error"}
    return wrapper

@catch_errors
def average(request):
    nums = request["nums"]
    return 200, {"avg": sum(nums) / len(nums)}

print(average({"path": "/avg", "nums": [2, 4]}))
print(average({"path": "/avg", "nums": []}))
print(LOG)
Output
(200, {'avg': 3.0})
(500, {'error': 'internal error'})
['/avg: ZeroDivisionError: division by zero']
Step by step
  1. L7Any crash inside the handler lands here.
  2. L8Keep the details for the developers.
  3. L9The client learns only that something went wrong.
  4. L18Dividing by len([]) = 0 raises ZeroDivisionError, which becomes a 500.

Common mistakes

Forgetting to return the response

A wrapper that calls handler(request) but doesn't return it makes every route return None.

Leaking error details

return 500, {"error": str(e)} can show SQL, file paths or secrets to anyone. Log the details; send a generic message.

Decorator order

The top decorator is the outermost. Put error handling outside logging if you want crashes logged with their status.

Quick quiz

1. What does `@logged` above `def home` do?

  1. Ahome = logged(home)
  2. BCalls logged every time the file loads
  3. CLogs the source code of home
Show the answer

Answer: A

A decorator replaces the function with whatever the decorator returns.

2. What does this print?

def shout(handler):
    def wrapper(x):
        return handler(x).upper()
    return wrapper

def exclaim(handler):
    def wrapper(x):
        return handler(x) + "!"
    return wrapper

@shout
@exclaim
def greet(name):
    return "hi " + name

print(greet("ann"))
  1. A
    HI ANN!
  2. B
    HI ANN
  3. C
    hi ann!
Show the answer

Answer: A

exclaim wraps greet first ("hi ann!"), then shout uppercases the whole thing.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictFollow the layers

    Type exactly what this prints.

  2. ModifyStop leaking errors

    This error middleware sends the exception text to the client. Change it so the client gets 500 {"error": "internal error"}, and the details "<ExceptionType>: <message>" are appended to LOG instead.

  3. WriteRequire JSON

    Write a decorator require_json(handler). The wrapper receives a request dict with "headers" and "body":

    • if request["headers"].get("Content-Type") isn't "application/json" → return (415, {"error": "expected JSON"})
    • if the body isn't valid JSON → return (400, {"error": "invalid JSON"})
    • otherwise set request["json"] to the parsed body and return handler(request)

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
Middleware中间件Code that wraps every request: logging, auth, errors.
Decorator装饰器@name above a def: replaces the function with a wrapped version.