F
FasORM
v0.1.0

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!

← Step 2: Models Documentation Home →