python
59 lines · 10 steps
Building a paginated JSON API with Flask MethodView
A class-based Flask view exposes a collection endpoint that lists articles with pagination links and creates new ones.
Explained by
highlit
1from flask import Blueprint, request, url_for, jsonify, abort
2from flask.views import MethodView
3
4from .models import Article, db
5from .schemas import ArticleSchema
6
7articles_bp = Blueprint("articles", __name__, url_prefix="/api/articles")
8article_schema = ArticleSchema()
9
10
11class ArticleListAPI(MethodView):
12 init_every_request = False
13
14 def get(self):
15 page = request.args.get("page", 1, type=int)
16 per_page = min(request.args.get("per_page", 20, type=int), 100)
17
18 query = Article.query.order_by(Article.published_at.desc())
19 if author := request.args.get("author"):
20 query = query.filter_by(author=author)
21
22 pagination = query.paginate(page=page, per_page=per_page, error_out=False)
23
24 def page_url(p):
25 return url_for("articles.list", page=p, per_page=per_page, _external=True)
26
27 return jsonify({
28 "data": article_schema.dump(pagination.items, many=True),
29 "meta": {
30 "page": pagination.page,
31 "per_page": pagination.per_page,
32 "total": pagination.total,
33 "pages": pagination.pages,
34 },
35 "links": {
36 "self": page_url(page),
37 "next": page_url(pagination.next_num) if pagination.has_next else None,
38 "prev": page_url(pagination.prev_num) if pagination.has_prev else None,
39 },
40 })
41
42 def post(self):
43 errors = article_schema.validate(request.get_json() or {})
44 if errors:
45 abort(422, description=errors)
46
47 article = article_schema.load(request.get_json())
48 db.session.add(article)
49 db.session.commit()
50
51 response = jsonify(article_schema.dump(article))
52 response.status_code = 201
53 response.headers["Location"] = url_for(
54 "articles.detail", article_id=article.id, _external=True
55 )
56 return response
57
58
59articles_bp.add_url_rule("/", view_func=ArticleListAPI.as_view("list"))
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Class-based MethodView maps HTTP verbs to methods, keeping list and create logic on one endpoint.
- 2Building hypermedia links from url_for keeps pagination self-describing and decoupled from URL structure.
- 3Validate before load and return 201 with a Location header to follow REST conventions for resource creation.
Related explainers
python
from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from sqlalchemy.orm import Session
Chaining auth dependencies in FastAPI
dependency-injection
jwt-authentication
authorization
Intermediate
8 steps
java
public final class DeepCopier { private static final ObjectMapper MAPPER = new ObjectMapper() .registerModule(new JavaTimeModule())
Deep-copying objects via Jackson serialization
serialization
deep-copy
generics
Intermediate
7 steps
python
import threading from functools import wraps
A thread-safe debounce decorator in Python
decorators
debounce
threading
Advanced
6 steps
python
from urllib.parse import urlparse, parse_qs from dataclasses import dataclass, field ALLOWED_SCHEMES = {"http", "https"}
Validating URLs into a safe endpoint in Python
input validation
url parsing
dataclasses
Intermediate
7 steps
python
from flask import Blueprint, render_template, request, redirect, url_for, flash from werkzeug.security import check_password_hash from .models import User, db
A change-email route in Flask
blueprints
form-validation
password-hashing
Intermediate
8 steps
go
package render import ( "net/http"
How Gin renders MsgPack responses
serialization
content-type
interface-implementation
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-paginated-json-api-with-flask-methodview-explained-python-6859/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.