Build a Perfect Blog with FastAPI: Filter by Tag
Wenhao Wang
Dev Intern · Leapcell

In the previous article, we added the functionality to create and display tags for our blog.
Next, we will complete the remaining part of the tag functionality: filtering posts by tags.
When a user clicks on a tag link, we will take them to a new page that displays only the posts under that specific tag. To do this, we need to create a new route and handling logic in the backend, as well as a corresponding view in the frontend.
Step 1: Extend the Service Logic
First, we need to add two new methods to tags_service.py
: one to find a tag by its ID (to get its name), and another to find all associated posts by a tag ID.
Open the tags_service.py
file and add the following content:
# tags_service.py import uuid from typing import List from sqlmodel import Session, select from models import Tag, Post, PostTagLink # Import Post and PostTagLink def find_or_create_tags(tag_names: List[str], session: Session) -> List[Tag]: """ Find or create tag entities based on a list of tag names. """ # ... this function remains unchanged ... tags = [] if not tag_names: return tags statement = select(Tag).where(Tag.name.in_(tag_names)) existing_tags = session.exec(statement).all() tags.extend(existing_tags) existing_tag_names = {tag.name for tag in existing_tags} new_tag_names = [name for name in tag_names if name not in existing_tag_names] for name in new_tag_names: new_tag = Tag(name=name) session.add(new_tag) tags.append(new_tag) session.commit() for tag in tags: if tag.id is None: session.refresh(tag) return tags def get_tag_by_id(tag_id: uuid.UUID, session: Session) -> Tag | None: """Find a single tag by its ID""" return session.get(Tag, tag_id) def get_posts_by_tag_id(tag_id: uuid.UUID, session: Session) -> List[Post]: """Find all associated posts by a tag ID""" statement = ( select(Post) .join(PostTagLink, Post.id == PostTagLink.post_id) .where(PostTagLink.tag_id == tag_id) .order_by(Post.createdAt.desc()) ) posts = session.exec(statement).all() return posts
Code Explanation:
get_tag_by_id
: A simple helper function to get a tag object by its primary key.get_posts_by_tag_id
: This is the core query logic. We use SQLModel'sselect
andjoin
methods to filter out allPost
objects associated with the giventag_id
through thePostTagLink
association table, and we order them by creation time in descending order.
Step 2: Create the Tag Route
Now, let's implement the route to handle /tags/{tag_id}
requests.
First, create a new file named tags.py
inside the routers
folder.
# routers/tags.py import uuid from fastapi import APIRouter, Request, Depends from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from sqlmodel import Session from database import get_session from auth_dependencies import get_user_from_session import tags_service import tracking_service router = APIRouter() templates = Jinja2Templates(directory="templates") @router.get("/tags/{tag_id}", response_class=HTMLResponse) def get_posts_by_tag( request: Request, tag_id: uuid.UUID, session: Session = Depends(get_session), user: dict | None = Depends(get_user_from_session), ): # 1. Get all posts under this tag posts = tags_service.get_posts_by_tag_id(tag_id, session) # 2. Get the tag information to display the tag name on the page tag = tags_service.get_tag_by_id(tag_id, session) # 3. Batch get view counts for the post list to maintain consistency with the homepage post_ids = [post.id for post in posts] view_counts = tracking_service.get_counts_by_post_ids(post_ids, session) for post in posts: post.view_count = view_counts.get(post.id, 0) tag_name = tag.name if tag else "Unknown" return templates.TemplateResponse( "posts-by-tag.html", { "request": request, "posts": posts, "user": user, "filter_name": tag_name, "title": f"Posts in {tag_name}", }, )
Finally, don't forget to include this new router module in main.py
.
# main.py # ... other imports from routers import posts, users, auth, comments, uploads, tags # import the tags router # ... # Include routers app.include_router(posts.router) app.include_router(users.router) app.include_router(auth.router) app.include_router(comments.router) app.include_router(uploads.router) app.include_router(tags.router) # Mount the tag router
Step 3: Create the Frontend View
The final step is to create a posts-by-tag.html
view file in the templates
folder. This file will be used to display the list of posts filtered by a tag, and its content will be very similar to index.html
.
Create the templates/posts-by-tag.html
file:
{% include "_header.html" %} <div class="filter-header"> <h2>Posts in Tag: <strong>{{ filter_name }}</strong></h2> </div> {% if posts %} <div class="post-list"> {% for post in posts %} <article class="post-item"> <h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2> <p>{{ post.content[:150] }}...</p> <small>{{ post.createdAt.strftime('%Y-%m-%d') }} | Views: {{ post.view_count }}</small> </article> {% endfor %} </div> {% else %} <p>No posts found in this tag.</p> {% endif %} <a href="/" class="back-link" style="margin-top: 2rem;">← Back to Home</a> {% include "_footer.html" %}
This template will dynamically display a title (e.g., "Posts in Tag: Python") and the list of posts under that tag. If there are no posts under the tag, it will display a corresponding message.
Running and Testing
Restart your application:
uvicorn main:app --reload
Open your browser, navigate to a post that has tags, and then click on any of the tag links below the post.
You will be redirected to the corresponding tag's filter page and see a list of all posts under that tag.
With these two tutorials, we have added a complete tagging system to our blog.
At this point, our FastAPI blog project has covered everything from basic infrastructure to core features, content organization, and data analysis.
The potential for blog features is limitless. Based on the current framework, you can continue to add more functionalities. The rest is up to your imagination!
Don't forget to deploy on Leapcell—it provides FastAPI support, a PostgreSQL database, Redis, web analytics, and all the tools you need to build web applications.