Flask-Caching lets you store responses to frequently requested URLs in Redis, so the next time a user hits that URL, Flask can grab the response directly from Redis instead of re-running your Python code.

Let’s see it in action. Imagine a simple Flask app that simulates a slow API call:

from flask import Flask, jsonify
from flask_caching import Cache
import time

app = Flask(__name__)
cache = Cache(app, config={'CACHE_TYPE': 'redis', 'CACHE_REDIS_URL': 'redis://localhost:6379/0'})

@app.route('/slow_data/<int:item_id>')
@cache.cached(timeout=60) # Cache for 60 seconds
def get_slow_data(item_id):
    print(f"--- Generating data for item {item_id} ---")
    time.sleep(5) # Simulate a slow operation
    return jsonify({"item_id": item_id, "data": f"This is data for item {item_id}", "timestamp": time.time()})

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

When you first request /slow_data/123, you’ll see the --- Generating data for item 123 --- message printed in your console, and it will take about 5 seconds to return. If you immediately request /slow_data/123 again, the message won’t appear, and the response will be nearly instantaneous. The data for item_id=123 is now sitting in Redis. If you wait 61 seconds and request it again, you’ll see the message print and the 5-second delay again, because the cache has expired.

This caching mechanism is incredibly powerful for improving application performance by reducing redundant computations and database queries. The core idea is to identify parts of your application that are frequently accessed and don’t change often, and then use a fast, in-memory data store like Redis to serve those parts directly. Flask-Caching abstracts away the complexities of interacting with Redis, allowing you to focus on your application logic.

The CACHE_TYPE configuration is your first lever. 'redis' tells Flask-Caching to use Redis as the backend. The CACHE_REDIS_URL specifies how to connect: redis://localhost:6379/0 means connect to a Redis server running on your local machine, on port 6379, using database 0. Other common types include 'simple' (a basic in-memory cache, good for development but not production) and 'memcached'.

The @cache.cached(timeout=60) decorator is where the magic happens for individual routes. timeout=60 means that for any given URL pattern and its arguments, the result will be stored in Redis for 60 seconds. If the same URL is requested again within that minute, Flask-Caching intercepts the request, checks Redis, and returns the cached response without ever executing the get_slow_data function. The key used in Redis is automatically generated based on the route and its arguments (e.g., flask-cache-v2:your_app_name:your_route_name:item_id=123).

You can also use cache.set('my_key', my_value, timeout=300) and cache.get('my_key') to manually cache and retrieve arbitrary data, not just full responses. This is useful for caching results of expensive computations or external API calls that aren’t directly tied to a Flask route.

When a cache key is generated for a route, it’s derived from the function name, the arguments passed to the function, and potentially the request context. If your route has multiple arguments, like @app.route('/items/<int:category>/<int:item_id>'), the cache key will uniquely identify the combination of category and item_id. This ensures that caching is granular and doesn’t serve data for one item when another was requested.

A common pitfall is forgetting to set a CACHE_KEY_PREFIX in your Flask app’s configuration. If you have multiple Flask applications all using the same Redis instance, without a prefix, their cache keys could collide, leading to unexpected behavior. For example, app.config['CACHE_KEY_PREFIX'] = 'my_app_v1' would prepend my_app_v1: to all keys generated by this application.

The timeout parameter is critical. Setting it too high can lead to stale data being served, while setting it too low negates much of the performance benefit. The optimal value depends entirely on how frequently the underlying data changes and how critical it is for users to see the absolute latest information. For data that changes every few minutes, a timeout of 30 or 60 seconds might be appropriate. For data that rarely changes, you might set it to hours or even days.

Another advanced technique is using cache.delete('my_key') or cache.delete_many(['key1', 'key2']) to explicitly invalidate cache entries when the underlying data is updated. This is crucial for maintaining data consistency. For instance, if a user updates their profile, you’d want to delete the cached profile data so the next request fetches the fresh, updated version.

The most surprising thing about how flask-caching builds its keys is that it’s not just a simple concatenation of route and arguments. It uses a sophisticated hashing mechanism that takes into account the function’s signature and the actual values of the arguments, including complex objects if they are passed. This ensures that even subtle differences in input lead to distinct cache entries, preventing incorrect data from being served.

The next step in optimizing your application’s performance might involve exploring distributed caching strategies or looking into more advanced caching patterns like cache invalidation strategies beyond simple timeouts.

Want structured learning?

Take the full Flask course →