Skip to main content

Building RESTful APIs with MethodView

When building a REST API in Flask, using a single view function with multiple if request.method == 'GET': branches can lead to complex, hard-to-maintain code. MethodView solves this by automatically dispatching requests to class methods named after the corresponding HTTP verbs.

Defining a RESTful Resource

To create a resource, subclass MethodView and implement methods for the HTTP verbs you want to support (e.g., get, post, put, delete). Flask automatically populates the methods attribute based on the methods you define.

from flask import session, redirect, url_for
from flask.views import MethodView

class CounterAPI(MethodView):
def get(self):
# Handles GET requests
return str(session.get("counter", 0))

def post(self):
# Handles POST requests
session["counter"] = session.get("counter", 0) + 1
return redirect(url_for("counter"))

# Register the view
app.add_url_rule(
"/counter", view_func=CounterAPI.as_view("counter")
)

The as_view method converts the class into a view function that can be passed to add_url_rule. The name passed to as_view (e.g., "counter") is used for URL generation with url_for.

Composing Functionality with Inheritance

You can organize common logic into base classes and combine them using standard Python inheritance. MethodView merges the supported HTTP methods from all base classes.

from flask.views import MethodView

class GetView(MethodView):
def get(self):
return "GET"

class DeleteView(MethodView):
def delete(self):
return "DELETE"

class GetDeleteView(GetView, DeleteView):
"""Supports both GET and DELETE"""
pass

app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))

Using Async Handler Methods

MethodView supports asynchronous handler methods. Flask uses current_app.ensure_sync in dispatch_request to execute these methods correctly within the request context.

import asyncio
from flask.views import MethodView

class AsyncUserAPI(MethodView):
async def get(self, user_id):
await asyncio.sleep(0.1) # Simulate async DB call
return {"id": user_id, "name": "Flask User"}

app.add_url_rule("/user/<int:user_id>", view_func=AsyncUserAPI.as_view("user"))

Applying Decorators

Because MethodView is a class, you cannot use the @app.route decorator directly on its methods. Instead, define a decorators class attribute containing a list of decorators to apply to the generated view function.

from flask import make_response
from flask.views import MethodView

def add_x_parachute(f):
def new_function(*args, **kwargs):
resp = make_response(f(*args, **kwargs))
resp.headers["X-Parachute"] = "awesome"
return resp
return new_function

class DecoratedView(MethodView):
decorators = [add_x_parachute]

def get(self):
return "Decorated response"

app.add_url_rule("/decorated", view_func=DecoratedView.as_view("decorated"))

Optimizing Performance

By default, Flask creates a new instance of your MethodView class for every request. If your view is expensive to initialize or doesn't store request-specific state on self, you can set init_every_request = False to reuse a single instance.

from flask.views import MethodView

class OptimizedView(MethodView):
init_every_request = False

def __init__(self):
# This will only run once when as_view is called
self.shared_data = "initialized"

def get(self):
return self.shared_data

app.add_url_rule("/optimized", view_func=OptimizedView.as_view("optimized"))

[!WARNING] When init_every_request = False, you must not store request-specific data on self (e.g., self.user = ...), as this will leak between requests. Use flask.g for request-bound data instead.

Automatic HEAD Handling

If you implement a get method but not a head method, MethodView automatically handles HEAD requests by calling your get method and discarding the body. This behavior is implemented in dispatch_request:

# Internal logic in flask/views.py
def dispatch_request(self, **kwargs):
meth = getattr(self, request.method.lower(), None)

if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)

assert meth is not None, f"Unimplemented method {request.method!r}"
return current_app.ensure_sync(meth)(**kwargs)

If a request is made with a method that is not implemented (and is not a HEAD request falling back to GET), an AssertionError is raised. However, Flask typically prevents this by returning a 405 Method Not Allowed response because the methods attribute is correctly populated during class definition.