Hello! Welcome back.
In our last lesson, we put a critical piece of our application's foundation in place: the get_async_session dependency. This function provides a clean, safe, and efficient way to manage database sessions for every API request.
Today, we will put that dependency to work. Our goal is to implement a 'list' endpoint to retrieve all records from a database table. This is often the first "read" operation you'll build in any CRUD (Create, Read, Update, Delete) application. We'll fetch all the posts from our posts table and return them as a JSON array.

1. Anatomy of the "List" Endpoint
To create an endpoint that returns a list of items, we need to combine several concepts we've already touched upon:
- Path Operation Decorator: We'll use
@router.get("/posts")to define the HTTP method (GET) and the path (/posts). - Dependency Injection: We will use
db: AsyncSession = Depends(get_async_session)in our function signature to get a database session. - Response Model: This is a key part. Since our endpoint will return multiple posts, not just one, we need to tell FastAPI that the response will be a list of our Pydantic
PostReadschema. We do this withresponse_model=list[PostRead].
The video below, although using a synchronous approach, clearly demonstrates setting the response_model to a list of Pydantic models. The concept is identical in our asynchronous implementation.
FastAPI SQLAlchemy Tutorial 2025 — Build a REST API with SQL
Watch this short clip from 'FastAPI SQLAlchemy Tutorial 2025' to see how the response_model is defined for an endpoint that returns multiple items.
Watch from 00:30:25 to 00:30:55. Focus on how response_model is set to list[UserResponse] to handle multiple results.
2. Asynchronous Querying with SQLAlchemy 2.0
Now for the core logic: how do we actually ask the database for all the posts? With SQLAlchemy's modern async support, this involves a clear, three-step process inside our endpoint function.
select(Post): We use theselectfunction from SQLAlchemy to create aSelectstatement object. This object describes our query—"I want to select all columns from thePosttable"—but it doesn't actually run it yet.await db.execute(...): We pass theSelectobject to thedb.execute()method. Since this is a network operation (talking to the database), weawaitit. This is the point where the query is sent to the database and executed..scalars().all(): The result ofexecuteis aResultobject that contains the rows returned by the database..scalars()extracts the first item from each row, which in our case is the fullPostORM object that SQLAlchemy constructs..all()fetches all of these scalar results and puts them into a standard Python list.
The following video is an excellent, concise demonstration of this exact process. It shows how to build an async "list" endpoint from scratch using this modern syntax.
How to Use Async SQLAlchemy in FastAPI
This video from Pretty Printed, 'How to Use Async SQLAlchemy in FastAPI', perfectly demonstrates the modern async query syntax we will be using.
Watch from 11:55 to 13:20. Pay close attention to the conversion of the get_users endpoint to use await db.execute(select(User)) followed by .scalars().all().
This pattern is the standard for async queries in SQLAlchemy 2.0. To reinforce this, the following article shows the exact same select -> execute -> scalars pattern in a text-based format.
Build an async python service with FastAPI & SQLAlchemy
The article 'Build an async python service with FastAPI & SQLAlchemy' provides another example of this pattern in the context of a 'book store' application.
In the code snippet for app.py, find the get_books endpoint. Notice how it follows the same logic: it creates a DAL, which inside uses db.execute(select(Book)) and result.scalars().all() to fetch the data. We'll be implementing this logic directly in our endpoint for simplicity.
3. Hands-On: Implementing the GET /posts Endpoint
Let's put this all together. We'll create a new router file for our post-related endpoints.
- If you haven't already, create a directory named
routersin your project's root. - Inside the
routersdirectory, create a new file namedposts.py. - Add the following code to
routers/posts.py:
# routers/posts.py
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
# Import our models and schemas
from models import Post
from schemas import PostRead
from database import get_async_session
# Create a new router
router = APIRouter()
@router.get("/posts", response_model=list[PostRead])
async def get_all_posts(db: AsyncSession = Depends(get_async_session)):
"""
Get all posts from the database.
"""
# Create a select statement
query = select(Post)
# Execute the query
result = await db.execute(query)
# Get the scalar results (the Post objects) and convert to a list
posts = result.scalars().all()
return posts
- Now, you need to include this new router in your main application file (
main.py).
# main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
from database import create_db_and_tables
from routers import posts # Import the posts router
# Lifespan function to create DB tables on startup
@asynccontextmanager
async def lifespan(app: FastAPI):
print("Creating database and tables...")
await create_db_and_tables()
yield
print("Shutting down...")
app = FastAPI(lifespan=lifespan)
# Include the posts router
app.include_router(posts.router)
@app.get("/")
def read_root():
return {"message": "Welcome to the Blog API"}
Testing your Endpoint
Start your application with uvicorn:uvicorn main:app --reload
Navigate to http://127.0.0.1:8000/docs. You should now see your new /posts endpoint listed.
Try it out! Click "Execute". The response will likely be an empty array [], because our database is empty. That's perfectly fine! We've successfully built the endpoint to read the data; we just haven't added the endpoint to create data yet. This empty response proves our code is working correctly.
The JSON data you see is what a frontend application would use to display a list of blog posts, perhaps in a table like the one shown below.

Conclusion
Excellent work! You have successfully created your first database-driven API endpoint. You've learned how to combine path operations, dependency injection, and Pydantic response models with the modern, asynchronous querying style of SQLAlchemy 2.0.
Key Takeaways:
- To return a list of items, set the
response_modeltolist[YourPydanticSchema]. - The asynchronous query pattern is:
- Create a query with
select(). - Execute it with
await db.execute(). - Extract the ORM objects with
.scalars().all().
- Create a query with
- Routers (
APIRouter) help organize your endpoints into logical modules (e.g., all post-related endpoints go inrouters/posts.py).
In the next lesson, we'll build on this by implementing the second fundamental "read" operation: retrieving a single record by its ID. This will introduce us to path parameters and how to add a WHERE clause to our database queries.