RealWorldApp - part 3
- 961 words
- 5 min
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.
| id | slug | title | description | body | created_at | updated_at | author_id |
|---|---|---|---|---|---|---|---|
| 1 | how-to-learn-sql-in-2025 | How to Learn SQL in 2025 | A practical guide to mastering SQL databases | "SQL remains the backbone of data management. In this guide, we cover..." | 2025-01-15 10:00:00 | 2025-01-16 11:30:00 | 1 |
| 2 | react-hooks-deep-dive | React Hooks: A Deep Dive | Everything you need to know about React Hooks in 2025 | "Hooks revolutionized React development. Let's explore useState, useEffect..." | 2025-02-20 14:15:00 | 2025-02-22 09:45:00 | 1 |
| 3 | javascript-es2025-features | ES2025: New JavaScript Features | The latest additions to JavaScript | "ES2025 introduces pattern matching, new array methods, and..." | 2025-03-10 08:30:00 | 2025-03-12 15:20:00 | 2 |
| 4 | web-performance-optimization | Web Performance Optimization in 2025 | Make your websites faster | "From lazy loading to HTTP/3, discover modern techniques to..." | 2025-03-25 16:40:00 | 2 | |
| 5 | fullstack-developer-roadmap | Full-Stack Developer Roadmap 2025 | A guide to becoming a full-stack dev | "Frontend, backend, DevOps... Here's your complete roadmap." | 2025-04-05 09:10:00 | 2025-04-07 10:00:00 | 3 |
| 6 | docker-for-beginners | Docker for Beginners | Learn Docker from scratch | Containers are essential for modern development. Let's get started... | 2025-04-18 13:25:00 | 3 | |
| 7 | tech-and-life-balance | Tech and Life Balance | How to stay sane in tech | Burnout is real. Here are my tips to maintain equilibrium... | 2025-05-01 11:00:00 | 2025-05-03 17:30:00 | 4 |
| 8 | future-of-ai-in-2025 | The Future of AI in 2025 | What to expect from artificial intelligence | "From generative models to ethical concerns, AI is evolving fast..." | 2025-05-15 14:50:00 | 4 | |
| 9 | how-to-contribute-to-open-source | How to Contribute to Open Source | A beginner's guide | "GitHub, pull requests, and community norms explained..." | 2025-06-02 10:15:00 | 2025-06-05 08:45:00 | 4 |
| 10 | why-i-switched-to-vim | Why I Switched to Vim | My journey from VS Code to Vim | "After years of resistance, I finally saw the light..." | 2025-06-20 16:30:00 | 4 |
| article_id | tag_id |
|---|---|
| 1 | 1 |
| 1 | 5 |
| 2 | 2 |
| 2 | 4 |
| 3 | 2 |
| 4 | 3 |
| 5 | 1 |
| 5 | 3 |
| 6 | 1 |
| 7 | 1 |
| 8 | 1 |
| 8 | 2 |
| 9 | 1 |
| 9 | 2 |
| 10 | 1 |
| id | name |
|---|---|
| 1 | programming |
| 2 | javascript |
| 3 | webdev |
| 4 | react |
| 5 | database |
| id | password_hash | username | bio | image | |
|---|---|---|---|---|---|
| 1 | jake@realworld.io | $2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7Xy | jake | I work at statefarm | https://api.realworld.io/images/jake.jpg |
| 2 | alice@realworld.io | $2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7Xy | alice | Software engineer & tech lover | https://api.realworld.io/images/alice.jpg |
| 3 | bob@realworld.io | $2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7Xy | bob | Full-stack developer | Coffee addict | |
| 4 | john@realworld.io | $2a$10$f7V5zZQk6uZ1E6XyJ.OeOeQJz6Q9v7Xy3Z1lLmN4pQ7Xy | john | Writing about tech and life | https://api.realworld.io/images/john.jpg |
| id | user_id | article_id | created_at | updated_at | body |
|---|---|---|---|---|---|
| 1 | 2 | 1 | 2025-01-15 12:30:00 | Great guide! I've been using SQL for years and still learned something new. | |
| 2 | 3 | 1 | 2025-01-16 09:15:00 | Do you cover window functions? They're a game-changer! | |
| 3 | 4 | 2 | 2025-02-21 10:00:00 | I struggled with useEffect dependencies. This cleared things up! | |
| 4 | 1 | 2 | 2025-02-22 14:30:00 | "Glad you found it helpful, John!" | |
| 5 | 1 | 3 | 2025-03-11 11:20:00 | Pattern matching looks amazing! Can't wait to use it in production. | |
| 6 | 4 | 3 | 2025-03-12 16:45:00 | Do you think these features will be widely supported by browsers soon? | |
| 7 | 3 | 4 | 2025-03-26 08:30:00 | HTTP/3 is indeed a game-changer. Have you benchmarked it against HTTP/2? | |
| 8 | 1 | 4 | 2025-03-27 14:00:00 | "Not yet, but I plan to! Great suggestion." | |
| 9 | 2 | 5 | 2025-04-06 15:20:00 | This is gold for beginners! Maybe add a section on testing? | |
| 10 | 4 | 5 | 2025-04-07 10:10:00 | Testing is crucial. I'd love to see Jest vs. Cypress comparisons. | |
| 11 | 2 | 7 | 2025-05-02 12:00:00 | So relatable! I've been struggling with burnout lately. | |
| 12 | 3 | 7 | 2025-05-03 09:30:00 | "Your tip about ""deep work"" hours changed my productivity. Thanks!" | |
| 13 | 1 | 8 | 2025-05-16 11:15:00 | Fascinating read! What's your take on AI ethics regulations? | |
| 14 | 2 | 8 | 2025-05-17 14:40:00 | I think we'll see major laws in 2026. The EU is already drafting something. | |
| 15 | 2 | 10 | 2025-06-21 10:00:00 | Vim convert here too! Do you use any specific plugins? |
| user_id | article_id |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 1 | 3 |
| 4 | 5 |
| 2 | 8 |
| 3 | 10 |
| user_followed_id | user_following_id |
|---|---|
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 4 |
| 3 | 1 |
| 3 | 4 |
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.