🚀 Creating an API Endpoint to Fetch All Todos
Now that our database is set up and contains records, the next step is to create an API endpoint that retrieves all records from the database.
Let’s begin with the basic application setup.
🗂️ main.py
from fastapi import FastAPI
import models
from database import engine
app = FastAPI()
models.Base.metadata.create_all(bind=engine)🔎 What Does This Do?
models.Base.metadata.create_all(bind=engine)This line ensures that our tables are created in the database if they do not already exist.
If todos.db does not exist, it will be created automatically.
⚠️ Important:
If you modify your models (for example, adding new columns), this command will not automatically update existing tables.
During development, it’s often easier to:
Delete
todos.dbRestart the application
Let the tables be recreated
<aside> 💡
In future section we will learn how to enhance DB without deleting each time
</aside>
🔌 Creating a Database Dependency
To interact with the database safely, we need to create a database session dependency.
Update your imports and add the following function:
🗂️ main.py
from fastapi import FastAPI
import models
from database import SessionLocal, engine
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()🧠 What It Does
👉 Creates a new database session
👉 Gives it to the request handler
👉 Automatically closes it afterward
🔍 Step-by-Step Breakdown
1️⃣ Create a Session
db = SessionLocal()SessionLocalis a session factory (created withsessionmaker)It opens a connection to the database
This session is used to run queries
2️⃣ yield db
yield dbSends the session to the route function
FastAPI uses this for dependency injection
Example usage:
@app.get("/books")
def read_books(db: Session = Depends(get_db)):
return db.query(Book).all()👉 The route receives the same db session
3️⃣ Always Close the Session
finally:
db.close()Runs after the request finishes (success or error)
Releases the database connection
Prevents connection leaks
⚙️ Why Use yield Instead of return?
Because yield allows FastAPI to run cleanup code afterward.
👉 Think of it as:
“Give the session → do work → clean up automatically”
✅ In Simple Terms
🧩 This function manages the full lifecycle of a database session for each request:
Open → Use → Close
✅ This is safe
✅ This is efficient
✅ This is required in most production applications
We should only open a database connection when needed and close it immediately after use.
🏗️ Creating Our First API Endpoint
Now let’s create an endpoint that fetches all records.
We’ll use dependency injection to provide the database session automatically.
🗂️ main.py
from typing import Annotated
from sqlalchemy.orm import Session
from fastapi import FastAPI, Depends
import models
from models import Todos
from database import SessionLocal, engine
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/")
async def read_all(db: Annotated[Session, Depends(get_db)]):
return db.query(Todos).all()🧠 What It Does
👉 Gets a database session
👉 Queries the Todos table
👉 Returns all rows
💡 What Is Dependency Injection?
Dependency injection means:
We need something (a dependency) before we execute our main function.
In this case, our endpoint depends on:
A database session
So FastAPI:
Calls
get_db()Opens a database session
Injects it into the
read_allfunctionCloses it automatically afterward
All of this happens behind the scenes.
🔍 Step-by-Step Breakdown
1️⃣ db: Annotated[Session, Depends(get_db)]
This tells FastAPI to inject a database session into the function.
Session→ SQLAlchemy database session typeDepends(get_db)→ Callsget_db()to get the sessionAnnotated→ Modern Python way to attach dependency metadata
👉 FastAPI automatically provides db when the route is called.
2️⃣ db.query(Todos)
Queries the database table mapped by the
TodosmodelPrepares a SELECT statement
Equivalent SQL:
SELECT * FROM todos;3️⃣ .all()
Executes the query
Returns all matching rows as a list of objects
🖥️ Running the Application
Navigate into your project folder and run:
uvicorn main:app --reloadThen open your browser and visit:
/docsThis opens the automatically generated Swagger UI.
You will see one endpoint:
GET /Click Execute, and all records from your database will be returned.
♻️ Improving Dependency Injection (Cleaner Code)
Writing this every time:
db: Annotated[Session, Depends(get_db)]Can become repetitive.
Instead, we can create a reusable variable:
db_dependency = Annotated[Session, Depends(get_db)]Then simplify our endpoint:
db_dependency = Annotated[Session, Depends(get_db)]
@app.get("/")
async def read_all(db: db_dependency):
return db.query(Todos).all()Now, instead of rewriting the dependency logic for every endpoint, we simply use:
db: db_dependencyMuch cleaner and easier to maintain.
🧩 Why This Approach Is Powerful
With this setup:
Every request automatically gets a database session
The session closes safely after the response
We avoid duplicated code
Our API remains clean and scalable
FastAPI makes dependency injection simple by using standard Python type annotations.
🗂️ main.py
from typing import Annotated
from sqlalchemy.orm import Session
from fastapi import FastAPI, Depends
import models
from models import Todos
from database import SessionLocal, engine
app = FastAPI()
models.Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]
@app.get("/")
async def read_all(db: db_dependency):
return db.query(Todos).all()🫙database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.ext.declarative import declarative_base
SQLALCHEMY_DATABASE_URL = "sqlite:///./todos.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
)
Base = declarative_base()💡models.py
from database import Base
from sqlalchemy import Column, Integer, String, Boolean
class Todos(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False)
description = Column(String)
priority = Column(Integer)
complete = Column(Boolean, default=False)