RealWorldApp - part 3

Hi,

after RealWorldApp part 1 and part 2 comes part 3.

In part 2 we implemented our data model.

Let's check that it works!

Dummy data

Using Mistral AI we created dummy data to populate our database.

idslugtitledescriptionbodycreated_atupdated_atauthor_id
1how-to-learn-sql-in-2025How to Learn SQL in 2025A practical guide to mastering SQL databases"SQL remains the backbone of data management. In this guide, we cover..."2025-01-15 10:00:002025-01-16 11:30:001
2react-hooks-deep-diveReact Hooks: A Deep DiveEverything you need to know about React Hooks in 2025"Hooks revolutionized React development. Let's explore useState, useEffect..."2025-02-20 14:15:002025-02-22 09:45:001
3javascript-es2025-featuresES2025: New JavaScript FeaturesThe latest additions to JavaScript"ES2025 introduces pattern matching, new array methods, and..."2025-03-10 08:30:002025-03-12 15:20:002
4web-performance-optimizationWeb Performance Optimization in 2025Make your websites faster"From lazy loading to HTTP/3, discover modern techniques to..."2025-03-25 16:40:002
5fullstack-developer-roadmapFull-Stack Developer Roadmap 2025A guide to becoming a full-stack dev"Frontend, backend, DevOps... Here's your complete roadmap."2025-04-05 09:10:002025-04-07 10:00:003
6docker-for-beginnersDocker for BeginnersLearn Docker from scratchContainers are essential for modern development. Let's get started...2025-04-18 13:25:003
7tech-and-life-balanceTech and Life BalanceHow to stay sane in techBurnout is real. Here are my tips to maintain equilibrium...2025-05-01 11:00:002025-05-03 17:30:004
8future-of-ai-in-2025The Future of AI in 2025What to expect from artificial intelligence"From generative models to ethical concerns, AI is evolving fast..."2025-05-15 14:50:004
9how-to-contribute-to-open-sourceHow to Contribute to Open SourceA beginner's guide"GitHub, pull requests, and community norms explained..."2025-06-02 10:15:002025-06-05 08:45:004
10why-i-switched-to-vimWhy I Switched to VimMy journey from VS Code to Vim"After years of resistance, I finally saw the light..."2025-06-20 16:30:004
article_idtag_id
11
15
22
24
32
43
51
53
61
71
81
82
91
92
101
idname
1programming
2javascript
3webdev
4react
5database
idemailpassword_hashusernamebioimage
1jake@realworld.io$2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7XyjakeI work at statefarmhttps://api.realworld.io/images/jake.jpg
2alice@realworld.io$2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7XyaliceSoftware engineer & tech loverhttps://api.realworld.io/images/alice.jpg
3bob@realworld.io$2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7XybobFull-stack developer | Coffee addict
4john@realworld.io$2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7XyjohnWriting about tech and lifehttps://api.realworld.io/images/john.jpg
iduser_idarticle_idcreated_atupdated_atbody
1212025-01-15 12:30:00Great guide! I've been using SQL for years and still learned something new.
2312025-01-16 09:15:00Do you cover window functions? They're a game-changer!
3422025-02-21 10:00:00I struggled with useEffect dependencies. This cleared things up!
4122025-02-22 14:30:00"Glad you found it helpful, John!"
5132025-03-11 11:20:00Pattern matching looks amazing! Can't wait to use it in production.
6432025-03-12 16:45:00Do you think these features will be widely supported by browsers soon?
7342025-03-26 08:30:00HTTP/3 is indeed a game-changer. Have you benchmarked it against HTTP/2?
8142025-03-27 14:00:00"Not yet, but I plan to! Great suggestion."
9252025-04-06 15:20:00This is gold for beginners! Maybe add a section on testing?
10452025-04-07 10:10:00Testing is crucial. I'd love to see Jest vs. Cypress comparisons.
11272025-05-02 12:00:00So relatable! I've been struggling with burnout lately.
12372025-05-03 09:30:00"Your tip about ""deep work"" hours changed my productivity. Thanks!"
13182025-05-16 11:15:00Fascinating read! What's your take on AI ethics regulations?
14282025-05-17 14:40:00I think we'll see major laws in 2026. The EU is already drafting something.
152102025-06-21 10:00:00Vim convert here too! Do you use any specific plugins?
user_idarticle_id
21
32
13
45
28
310
user_followed_iduser_following_id
12
13
21
24
31
34

Test time

Database session

We'll need a database session dependency to manage our connections to the database. We define it in the app/db.py for the moment and put the definition of the functions responsible of dabase creation and deletion :

# app/db.py
from typing import Annotated

from fastapi import Depends
from sqlmodel import Session, SQLModel, create_engine

from app import models

sqlite_file_name = "database.db"
slqite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(slqite_url)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def drop_db():
    SQLModel.metadata.drop_all(engine)


def get_session():
    with Session(engine) as session:
        yield session


DbSessionDep = Annotated[Session, Depends(get_session)]

First creation of the FastAPI app

(finaly)

Currently our project is structured like this:

.
├── app
│   ├── db.py
│   ├── __init__.py
│   ├── main.py
│   └── models.py
├── CODE_OF_CONDUCT.md
├── database.db
├── favicon.ico
├── LICENSE
├── logo.png
├── pyproject.toml
├── README.md
└── uv.lock

Let's change app/main.py to finally create a FastAPI app. We keep the previous ability to create the database but keep it away and a drop database could also be useful:

from fastapi import FastAPI


app = FastAPI()

Now let's add some route to read our database.

Get Users

from fastapi import FastAPI, HTTPException
from sqlmodel import select

from app.db import DbSessionDep
from app.models import User
# ...
app = FastAPI()


@app.get("/api/users")
async def read_users(db_session: DbSessionDep):
    users = db_session.exec(select(User)).all()
    return users


@app.get("/api/user/{username}")
async def read_user(username: str, db_session: DbSessionDep):
    user = db_session.exec(select(User).where(User.username == username)).first()
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return user

Get Articles

# ...
from sqlalchemy.orm import aliased

from app.models import Article, User
# ...
@app.get("/api/articles")
async def read_articles(
    db_session: DbSessionDep,
    tag: str | None = None,
    author: str | None = None,
    favorited: str | None = None,
    limit: int | None = 20,
    offset: int | None = 0,
):
    statement = select(Article).offset(offset).limit(limit)
    if tag:
        statement = statement.join(ArticlesHaveTags).join(Tag).where(Tag.name == tag)
    if author:
        user_author = aliased(User, name="u_author")
        statement = statement.join(user_author).where(user_author.username == author)
    if favorited:
        fan = aliased(User, name="u_fan")
        statement = (
            statement.join(
                UsersFavourArticles,
                Article.id == UsersFavourArticles.article_id,
            )
            .join(fan)
            .where(fan.username == favorited)
        )
    articles = db_session.exec(statement).all()
    return articles


@app.get("/api/article/{slug}")
async def read_article(slug: str, db_session: DbSessionDep):
    article = db_session.exec(select(Article).where(Article.slug == slug)).first()
    if not article:
        raise HTTPException(status_code=404, detail="article not found")
    return article.tags

Get Tags

# ...
from app.models import Article, User, Tag
# ...
@app.get("/api/tags")
async def read_tags(db_session: DbSessionDep):
    tags = db_session.exec(select(Tag)).all()
    return tags


@app.get("/api/tag/{tag_id}")
async def read_tag(tag_id: int, db_session: DbSessionDep):
    tag = db_session.exec(select(Tag).where(Tag.id == tag_id)).first()
    if not tag:
        raise HTTPException(status_code=404, detail="tag not found")
    return tag

Get Comments from an article

# ...
from app.models import Article, User, Tag,
# ...
@app.get("/api/articles/{slug}/comments")
async def read_comments_from_article(slug: str, db_session: DbSessionDep):
    article = db_session.exec(select(Article).where(Article.slug == slug)).first()
    if not article:
        raise HTTPException(status_code=404, detail="article not found")
    comments = article.commenting_users
    return comments

Next

Great! We can read our data. In the next session we'll implement authentication.