Step 3: FastAPI Routes & Queries
In this final step, we will write Pydantic schemas for request validation and implement all RESTful Blog API route handlers in main.py.
1. Define Pydantic Request Schemas
app/schemas.py
from pydantic import BaseModel, EmailStr
from typing import Literal, Optional
class UserCreate(BaseModel):
name: str
email: EmailStr
password: str
class PostCreate(BaseModel):
title: str
body: str
user_id: int
status: Optional[Literal["draft", "published", "archived", "in-review"]] = "draft"
class CommentCreate(BaseModel):
body: str
user_id: int
2. Complete FastAPI Route Implementation
main.py
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Query
from fasorm import init_db
from app.models.blog import User, Post, Comment
from app.schemas import UserCreate, PostCreate, CommentCreate
@asynccontextmanager
async def lifespan(app: FastAPI):
init_db(os.getenv("DB_URL", "postgresql+asyncpg://postgres:secret@localhost:5432/blog_db"))
yield
app = FastAPI(title="FasORM Blog API", lifespan=lifespan)
# 1. Create User
@app.post("/users", status_code=201)
async def create_user(payload: UserCreate):
user = await User.create(**payload.model_dump())
return user.to_dict()
# 2. Create Post
@app.post("/posts", status_code=201)
async def create_post(payload: PostCreate):
post = await Post.create(**payload.model_dump())
return post.to_dict()
# 3. List Published Posts with Eager Author and Comment Count
@app.get("/posts")
async def list_posts(
status: str = Query("published"),
page: int = Query(1, ge=1),
per_page: int = Query(10, le=50)
):
# Eager-load author user, count comments, and paginate
paginated = await Post.with_("user") \
.with_count("comments") \
.where(status=status) \
.latest() \
.paginate(per_page=per_page, page=page)
return paginated.to_dict()
# 4. Get Post Details with Author and Full Comments
@app.get("/posts/{post_id}")
async def get_post(post_id: int):
post = await Post.with_("user", "comments").where(id=post_id).first()
if not post:
raise HTTPException(status_code=404, detail="Post not found")
return post.to_dict()
# 5. Add Comment to Post
@app.post("/posts/{post_id}/comments", status_code=201)
async def add_comment(post_id: int, payload: CommentCreate):
post = await Post.find(post_id)
if not post:
raise HTTPException(status_code=404, detail="Post not found")
comment = await Comment.create(post_id=post_id, **payload.model_dump())
return comment.to_dict()
# 6. User Stats with Aggregates
@app.get("/users/{user_id}/stats")
async def user_stats(user_id: int):
user = await User.find(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
total_posts = await Post.where(user_id=user_id).count()
published_posts = await Post.where(user_id=user_id, status="published").count()
total_comments = await Comment.where(user_id=user_id).count()
return {
"user_id": user_id,
"name": user.name,
"total_posts": total_posts,
"published_posts": published_posts,
"total_comments": total_comments
}
3. Test the Application
Run your FastAPI application using uvicorn:
Terminal
uvicorn main:app --reload
Visit http://127.0.0.1:8000/docs to test your endpoints via FastAPI's interactive Swagger UI!