Class-Based Views (MethodView)
Reviewed & published by Brayan K
A MethodView is a Flask class where each HTTP method — get, post, put, delete — becomes its own method, grouping the logic for one resource into a single tidy class.
Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll build a MethodView, register it with add_url_rule and as_view, share helper logic across methods, and learn exactly when a class beats a plain view function.
What You'll Learn in This Lesson
- Subclass flask.views.MethodView
- Write get and post methods for one resource
- Register a class with add_url_rule and as_view
- How Flask dispatches GET to get() automatically
- Share helpers across methods with a class
- Know when classes beat plain view functions
1 Your First MethodView
A class-based view turns the usual "one function per route" pattern on its head. You subclass MethodView and write a method named after each HTTP verb you want to support: a get method for GET requests, a post method for POST, and so on.
Flask handles the routing for you. When a request arrives, it looks at request.method and dispatches to the matching method automatically — GET calls get(), POST calls post(). There's no if request.method == "POST" branching to write or maintain.
To connect the class to a URL you call MyView.as_view("name"), which converts the class into a normal view function, and hand that to add_url_rule. The "name" string is the endpoint that url_for uses, so it must be unique across your app.
# app.py — a basic MethodView with get and post
from flask import Flask, request, jsonify
from flask.views import MethodView
app = Flask(__name__)
class GreetAPI(MethodView):
def get(self):
# Handles GET /greet
return "Hello from GET"
def post(self):
# Handles POST /greet
name = request.form.get("name", "friend")
return f"Hello, {name}, from POST"
# as_view("endpoint_name") turns the class into a view function
app.add_url_rule("/greet", view_func=GreetAPI.as_view("greet"))
# Simulate a GET and a POST without starting a server:
with app.test_client() as client:
print(client.get("/greet").data.decode()) # Hello from GET
print(client.post("/greet", data={"name": "Ada"}).data.decode()) # Hello, Ada, from POST
# app.run() would serve this forever, so it goes last and stays commented
# out — the test client above is how Flask itself exercises routes:
# if __name__ == "__main__":
# app.run(debug=True)
# ✅ Expected output:
# Hello from GET
# Hello, Ada, from POSTNotice there is no method-checking code — Flask sent the GET to get() and the POST to post() on its own. That automatic dispatch is the heart of a MethodView.
2 URL Parameters & Shared Helpers
URL parameters work exactly as they do with function views: declare them in the rule with <int:user_id> and accept them as arguments on the matching method, like def get(self, user_id).
Here's where a class starts to pay off. Because every method lives on the same class, they can share helper methods. A private _serialize method can format a resource once, and both get and a future put can call it — no duplicated logic scattered across separate functions.
You can also restrict which verbs a rule accepts by passing methods=["GET"] to add_url_rule, which is useful when one view function backs several rules.
# app.py — a URL parameter plus a shared helper method
from flask import Flask, jsonify
from flask.views import MethodView
app = Flask(__name__)
USERS = {1: "Ada", 2: "Grace"}
class UserAPI(MethodView):
def _serialize(self, user_id):
# Shared helper reused by methods — the win of a class
return {"id": user_id, "name": USERS.get(user_id, "unknown")}
def get(self, user_id):
return jsonify(self._serialize(user_id))
user_view = UserAPI.as_view("user_api")
app.add_url_rule("/users/<int:user_id>", view_func=user_view, methods=["GET"])
# jsonify sorts keys alphabetically by default:
with app.test_client() as client:
print(client.get("/users/1").get_json()) # {'id': 1, 'name': 'Ada'}
print(client.get("/users/2").get_json()) # {'id': 2, 'name': 'Grace'}
# app.run() would serve this forever, so it goes last and stays commented
# out — the test client above is how Flask itself exercises routes:
# if __name__ == "__main__":
# app.run(debug=True)
# ✅ Expected output:
# {'id': 1, 'name': 'Ada'}
# {'id': 2, 'name': 'Grace'}The _serialize helper centralizes how a user is formatted. As you add put or delete, they call the same helper instead of repeating the dictionary — that is the structural advantage of a class.
3 One Class, Many Routes (List + Create)
A real resource often lives at two URLs: /users for the collection (list all, create new) and /users/<id> for a single item. A single MethodView can serve all of them by registering the same view function on multiple rules.
The trick is defaults=. By giving /users a default of user_id=None, the get method can detect "no id was supplied" and return the whole list; when an id is present it returns one record. The post method creates a new user and returns a 201 Created status by returning a (body, status) tuple.
# app.py — list and create on one MethodView
from flask import Flask, request, jsonify
from flask.views import MethodView
app = Flask(__name__)
USERS = {1: "Ada"}
class UserAPI(MethodView):
def get(self, user_id):
if user_id is None:
return jsonify(sorted(USERS.values())) # list all
return jsonify(id=user_id, name=USERS[user_id])
def post(self):
name = request.form.get("name", "")
new_id = max(USERS) + 1
USERS[new_id] = name
return jsonify(created=new_id, name=name), 201
view = UserAPI.as_view("users")
# One view function, multiple rules:
app.add_url_rule("/users", defaults={"user_id": None}, view_func=view, methods=["GET"])
app.add_url_rule("/users", view_func=view, methods=["POST"])
app.add_url_rule("/users/<int:user_id>", view_func=view, methods=["GET"])
with app.test_client() as client:
print(client.get("/users").get_json()) # ['Ada']
r = client.post("/users", data={"name": "Linus"})
print(r.status_code, r.get_json()) # 201 {'created': 2, 'name': 'Linus'}
print(client.get("/users/2").get_json()) # {'id': 2, 'name': 'Linus'}
# app.run() would serve this forever, so it goes last and stays commented
# out — the test client above is how Flask itself exercises routes:
# if __name__ == "__main__":
# app.run(debug=True)
# ✅ Expected output:
# ['Ada']
# 201 {'created': 2, 'name': 'Linus'}
# {'id': 2, 'name': 'Linus'}This is the classic REST shape: GET /users lists, POST /users creates, and GET /users/<id> fetches one — all from a single, cohesive class.
🔬 MethodView Cheat Sheet
| You want to... | Use |
|---|---|
| Create the view class | class X(MethodView): |
| Handle GET | def get(self): |
| Handle POST | def post(self): |
| Register the view | app.add_url_rule("/path", view_func=X.as_view("name")) |
| Limit allowed methods | methods=["GET", "POST"] |
Rule of thumb: one resource with several HTTP methods, shared setup, or inheritance across endpoints → a MethodView. A single simple route → a plain @app.route function.
🎯 Your Turn: Fill in the Blanks
Complete the class-based view below. Replace each ___ so it subclasses MethodView, handles GET, and registers on /ping.
# 🎯 YOUR TURN: replace each ___ to finish the MethodView
from flask import Flask, jsonify
from flask.views import MethodView
app = Flask(__name__)
class PingAPI(___): # subclass the class-based view base
def ___(self): # which method name handles a GET?
return jsonify(status="ok", service="ping")
# turn the class into a view function and register it
app.add_url_rule("/ping", view_func=PingAPI.___("ping"))
with app.test_client() as client:
print(client.get("/ping").get_json()) # {'service': 'ping', 'status': 'ok'}Common Errors & Quick Reference
❌ Forgetting as_view (passing the class itself)
Passing view_func=MyView instead of view_func=MyView.as_view("name") hands Flask a class, not a callable view, and routing breaks. Always call as_view("unique_name") first — it returns the actual view function Flask expects.
❌ 405 Method Not Allowed
A 405 means the verb isn't served here. Either the matching method (for example post) isn't defined on the class, or your explicit methods=[...] list left it out. Define the method and include its verb in the rule's methods list.
| Task | Code |
|---|---|
| Subclass the base | class X(MethodView): |
| Register correctly | view_func=X.as_view("name") |
| Allow GET + POST | methods=["GET", "POST"] |
| Return a 201 | return body, 201 |
🏆 Mini Challenge: A Tiny Task API
Build a TaskAPI class-based view that lists tasks and adds new ones.
- Subclass MethodView with a get that returns all tasks as JSON
- Add a post that appends a task from the form and returns 201
- Register it on /tasks, then add a task and list them
# 🏆 Mini Challenge: a class-based Task API
from flask import Flask, request, jsonify
from flask.views import MethodView
app = Flask(__name__)
TASKS = ["Write tests"]
class TaskAPI(MethodView):
def get(self):
# List every task
return jsonify(tasks=TASKS)
def post(self):
# Add a task from the posted form data
task = request.form.get("task", "")
TASKS.append(task)
return jsonify(added=task, count=len(TASKS)), 201
app.add_url_rule("/tasks", view_func=TaskAPI.as_view("tasks"))
# Try it: list, add, then list again
with app.test_client() as client:
print(client.get("/tasks").get_json()) # {'tasks': ['Write tests']}
r = client.post("/tasks", data={"task": "Ship it"})
print(r.status_code, r.get_json()) # 201 {'added': 'Ship it', 'count': 2}
print(client.get("/tasks").get_json()) # {'tasks': ['Write tests', 'Ship it']}❓ Frequently Asked Questions
Lesson complete — you can structure views like a pro!
You can now subclass MethodView, write a method per HTTP verb, register classes with as_view and add_url_rule, share helpers across methods, and recognize when a class beats a plain function.
Practice quiz
What base class do you subclass for a class-based view?
- flask.Blueprint
- flask.Resource
- flask.ClassView
- flask.views.MethodView
Answer: flask.views.MethodView. Class-based views subclass flask.views.MethodView.
In a MethodView, which method handles a GET request?
- A method named get()
- A method named handle_get()
- A method named on_get()
- A method named GET()
Answer: A method named get(). Flask dispatches GET to the get() method automatically.
What converts a MethodView class into a usable view function?
- as_route()
- MyView.as_view("name")
- register_view()
- view_func()
Answer: MyView.as_view("name"). MyView.as_view("name") returns a view function you give to add_url_rule.
Which method registers the view on a URL?
- app.route_class()
- app.bind_view()
- app.add_url_rule()
- app.connect()
Answer: app.add_url_rule(). app.add_url_rule("/path", view_func=...) connects the view to a URL.
How does Flask decide which method to call?
- Alphabetical order
- The first method defined
- Random selection
- It dispatches based on request.method
Answer: It dispatches based on request.method. Flask inspects request.method and calls the matching method, e.g. POST -> post().
What is the main structural advantage of a MethodView?
- Methods share helper logic on the same class
- It is always faster than functions
- It needs no URL rule
- It disables HTTP methods
Answer: Methods share helper logic on the same class. Grouping verbs in one class lets them share helpers like a private _serialize method.
How do you declare a URL parameter for a MethodView rule?
- With request.args only
- With <int:user_id> in the rule and a matching method argument
- By naming the class after it
- It is not possible
Answer: With <int:user_id> in the rule and a matching method argument. Declare <int:user_id> in the rule and accept it as def get(self, user_id).
What does the string passed to as_view("name") become?
- The HTTP method
- The template name
- The endpoint name used by url_for
- The class docstring
Answer: The endpoint name used by url_for. It is the endpoint name, so it must be unique across the app.
How can one view function back several URL rules?
- By renaming the class each time
- It cannot, you need separate classes
- By disabling routing
- By calling add_url_rule multiple times with the same view function
Answer: By calling add_url_rule multiple times with the same view function. You can register the same as_view function on multiple rules.
How does a post() method return a 201 Created status?
- By returning a (body, status) tuple, e.g. jsonify(...), 201
- By raising Created()
- By setting self.status = 201
- By naming the method create()
Answer: By returning a (body, status) tuple, e.g. jsonify(...), 201. Returning a (body, 201) tuple sets the status code.
Continue this course
- Previous: Request Hooks (before/after_request)
- Next: Checkpoint: A Dynamic Web App — Combine templates, cookies, hooks and class-based views — then a quiz
- Quick reference: Flask cheat sheet
Frequently asked questions
What is a MethodView in Flask?
A MethodView is a class from flask.views that you subclass to build a view. Instead of writing one function that inspects request.method, you write a method per HTTP verb — get, post, put, delete — and Flask automatically dispatches each incoming request to the matching method. It groups all the logic for one resource (like a user or a task) into a single, readable class.
How do I register a MethodView with a URL?
Call the class method as_view("endpoint_name") to turn the class into a regular view function, then pass that to app.add_url_rule("/path", view_func=...). The string you give as_view becomes the endpoint name used by url_for, so it must be unique. You can attach the same view function to several rules to support patterns like /users and /users/<id>.
When should I use a class-based view instead of a function?
Reach for a MethodView when a single URL or resource handles several HTTP methods, when those methods share setup or helper logic, or when you want inheritance so multiple endpoints reuse common behavior. For a simple one-method route, a plain @app.route function is shorter and clearer — classes shine as the resource grows.
Why am I getting a 405 Method Not Allowed with a MethodView?
A 405 means the request used a method the view can't serve. Either the matching method (for example post) isn't defined on the class, or the rule's methods list omits it. MethodView infers allowed methods from the methods you define, but when you set methods=[...] explicitly on add_url_rule, that list must include every verb you want to accept.