Hey fellow Python devs! π
In this guide, weβll build a To-Do API using FastAPI with production-ready features. If you're looking to sharpen your backend development skills or add a solid project to your portfolio, this tutorial is for you!
You'll learn how to:
β
Set up a PostgreSQL database
β
Define models with SQLAlchemy
β
Validate data using Pydantic
β
Implement CRUD operations with FastAPI
Letβs get started! π
Project Setup
Step 1: Install Dependencies
Before we start coding, install the required dependencies:
pip install fastapi psycopg2 pydantic SQLAlchemy uvicorn python-dotenv
Step 2: Project Structure
Your project should be structured like this:
π todo_api_project
β£ π main.py # Entry point of the application
β£ π database.py # Database configuration
β£ π model.py # Database models
β£ π schema.py # Pydantic schemas
β£ π route.py # API routes
β π requirements.txt # Dependencies
Step 3: Setting Up the Database
Weβll use PostgreSQL as our database. Create a file called database.py
and add the following code:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "postgresql://yourusername:yourpassword@localhost/todo_db"
engine = create_engine(DATABASE_URL)
sessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
BASE = declarative_base()
def get_db():
db = sessionLocal()
try:
yield db
finally:
db.close()
Explanation
- We use
create_engine
to connect to the PostgreSQL database. -
sessionLocal
manages database sessions. -
BASE = declarative_base()
allows us to define database models. - The
get_db
function provides a database session when needed.
Step 4: Creating the To-Do Model
In model.py
, define the structure of your To-Do items using SQLAlchemy:
from sqlalchemy import Column, Integer, String, Boolean, TIMESTAMP, text
from database import BASE
class Todo(BASE):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False)
description = Column(String, nullable=True)
published = Column(Boolean, server_default=text("False"))
created_at = Column(TIMESTAMP(timezone=True), server_default=text("now()"))
Explanation
-
id
: Auto-generated primary key. -
title
: Required (nullable=False
). -
description
: Optional field (nullable=True
). -
published
: Boolean field with a default value ofFalse
. -
created_at
: Stores the creation timestamp.
Step 5: Creating Pydantic Schemas
In schema.py
, define how Pydantic will validate incoming request data:
from typing import Optional
from pydantic import BaseModel
class Todo(BaseModel):
title: str
description: Optional[str] = None
published: bool = False
class Config:
orm_mode = True
class UpdateTodo(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
published: Optional[bool] = None
Explanation
-
Todo
: Used for creating new tasks. -
UpdateTodo
: Used for updating tasks. -
orm_mode = True
: Ensures compatibility with SQLAlchemy models.
Step 6: Creating API Routes
Now, letβs create the API endpoints in route.py
:
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from database import get_db, engine
from schema import Todo, UpdateTodo
import model
from typing import List
# Create tables
model.BASE.metadata.create_all(bind=engine)
route = APIRouter()
1οΈβ£ Get All To-Dos
@route.get("/getalltodos")
def get_all_todo(db: Session = Depends(get_db)):
todos = db.query(model.Todo).all()
return todos if todos else []
β Fetches all tasks from the database.
2οΈβ£ Create a New To-Do
@route.post("/todo/create")
def create_todo(todo: Todo, db: Session = Depends(get_db)):
todo_item = model.Todo(**todo.model_dump())
db.add(todo_item)
db.commit()
db.refresh(todo_item)
return todo_item
β Takes JSON input and adds a new task to the database.
3οΈβ£ Update a To-Do
@route.put("/todo/update/{post_id}", response_model=UpdateTodo)
def update_todo(new_post: UpdateTodo, post_id: int, db: Session = Depends(get_db)):
todo = db.query(model.Todo).filter(model.Todo.id == post_id).first()
if not todo:
return {"error": "To-do not found"}
update_data = new_post.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(todo, key, value)
db.commit()
db.refresh(todo)
return todo
β
Updates an existing task based on post_id
.
4οΈβ£ Get Published To-Dos
@route.get("/published_todo")
def get_published_todos(db: Session = Depends(get_db)):
return db.query(model.Todo).filter(model.Todo.published == True).all()
β Fetches only published to-do items.
5οΈβ£ Get Draft To-Dos
@route.get("/draft_todo")
def get_draft_post(db: Session = Depends(get_db)):
return db.query(model.Todo).filter(model.Todo.published == False).all()
β Fetches only unpublished (draft) tasks.
6οΈβ£ Get a To-Do by ID
@route.get("/getbyid/{todo_id}")
def get_by_id(todo_id: int, db: Session = Depends(get_db)):
return db.query(model.Todo).filter(model.Todo.id == todo_id).first()
β
Fetches a specific task by its id
.
7οΈβ£ Search To-Dos
@route.get("/search", response_model=List[Todo])
def search_todo(name: str = Query(None), db: Session = Depends(get_db)):
query = db.query(model.Todo)
if name:
query = query.filter(model.Todo.title.like(f"%{name}%") | model.Todo.description.like(f"%{name}%"))
todos = query.all()
return todos if todos else {"error": "No post found"}
return []
β Searches tasks by title or description.
8οΈβ£ Delete a To-Do
@route.delete("/delete/{todo_id}")
def delete_post(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(model.Todo).filter(model.Todo.id == todo_id).first()
db.delete(todo)
db.commit()
β Deletes a task from the database.
Step 7: Running the API
Create main.py
to start the server:
from fastapi import FastAPI
from route import route
app = FastAPI()
@app.get("/")
def root():
return {"hello": "world!!!"}
app.include_router(route)
Run the server:
uvicorn main:app --reload
Visit http://127.0.0.1:8000/docs
to test the API! π
Conclusion
You've built a To-Do API with FastAPI, PostgreSQL, and SQLAlchemy! This is a great project to showcase in your portfolio. Keep exploring and refining your skills! π
Full code on my GitHub here
What do you think? Does this structure work for your blog? π
Top comments (0)