Python API Development with FastAPI

Advanced FastAPI

Get One Post Join

In this guide, we enhance our endpoint for retrieving an individual post by including the vote count in its JSON response. Previously, our endpoint for fetching multiple posts already used a join query to include votes, but the individual post retrieval did not incorporate this functionality.

Creating a New Post

Below is the code snippet that demonstrates how a new post is created:

new_post = models.Post(owner_id=current_user.id, **post.dict())
db.add(new_post)
db.commit()
db.refresh(new_post)

return new_post

Original Endpoint for Fetching a Single Post

The original implementation for retrieving a single post looked like this:

@router.get("/{id}", response_model=schemas.Post)
def get_post(id: int, db: Session = Depends(get_db), current_user: int = Depends(oauth2.get_current_user)):
    # cursor.execute("""SELECT * from posts WHERE id = %s """, (str(id),))
    post = db.query(models.Post).filter(models.Post.id == id).first()

    if not post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Post with id: {id} was not found")
    return post

During testing, the console logs generated were similar to the following:

WARNING:  WatchGodReload detected file change in '['C:\\Users\\sanje\\Documents\\Courses\\fastapi\\app\\routers\\post.py']'. Reloading...
INFO:     Started server process [10116]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:61470 - "GET /posts?limit=2 HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:61470 - "GET /posts?limit=2 HTTP/1.1" 200 OK

Integrating Vote Count with Join Queries

Since the JSON response did not include the vote count, we need to modify our query to join the votes table and count the votes. Here’s an example of the join query used for fetching multiple posts with their respective vote counts:

posts = db.query(models.Post, func.count(models.Vote.post_id).label("votes")).join(
    models.Vote, models.Vote.post_id == models.Post.id, isouter=True
).group_by(models.Post.id).filter(models.Post.title.contains(search)).limit(limit).offset(skip).all()
return posts

Updated Endpoint for a Single Post with Vote Count

Next, we update the individual post retrieval endpoint to include a join that fetches the vote count. The improved endpoint appears below:

@router.get("/{id}", response_model=schemas.PostOut)
def get_post(id: int, db: Session = Depends(get_db), current_user: int = Depends(oauth2.get_current_user)):
    # Fetch the post by its id
    post = db.query(models.Post).filter(models.Post.id == id).first()
    if not post:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail=f"Post with id: {id} was not found")
    
    # Join posts with votes to count the number of votes for this post
    result = db.query(models.Post, func.count(models.Vote.post_id).label("votes")).join(
        models.Vote, models.Vote.post_id == models.Post.id, isouter=True
    ).group_by(models.Post.id).filter(models.Post.id == id).first()
    
    return result

Note

The updated endpoint now returns a JSON structure that includes both the main post data and the associated vote count. This enhancement provides a more comprehensive view of the post’s engagement.

During testing, the console output confirmed that the endpoint was working as expected:

INFO:     127.0.0.1:61470 - "GET /posts/?limit=2 HTTP/1.1" 200 OK
INFO:     127.0.0.1:54833 - "GET /posts/10 HTTP/1.1" 200 OK

An example of a successful JSON response is:

{
  "Post": {
    "title": "asdf",
    "content": "sdfsf",
    "published": true,
    "id": 10,
    "created_at": "2021-08-28T21:49:28.150819-04:00",
    "owner_id": 23,
    "owner": {
      "id": 23,
      "email": "[email protected]",
      "created_at": "2021-08-28T21:03:56.927042-04:00"
    },
    "votes": 2
  }
}

Warning

Ensure that all variables used in your queries are properly defined within the current scope. Errors like NameError: name 'post' is not defined may occur if variables are misspelled or referenced before initialization.

While you could extend this functionality to the post creation and update endpoints so that they also return the vote count, it is usually sufficient to include it only when fetching posts.

By implementing these changes, the get individual post endpoint now provides both the detailed post data and its vote count, resulting in an improved and more informative API response.

Watch Video

Watch video content

Previous
Joins Sqlalchemy