Sunday, 23 November 2025

All in One - Hands-On!?!

FastAPI 12-Module Hands-On Worksheet — Full Course (Single Page)

FastAPI — 12 Module Hands-On Worksheet (Complete)

Student labs, step-by-step tasks, assessments, instructor answers & final project — single page.
Ready to paste into Blogger → Compose or HTML view

Quick Start (For Instructors)

Use this single page during a lab session. Each module contains: learning outcomes, step-by-step lab, practice questions and a mini assignment.

# Quick environment (one-time)
python -m venv .venv
# Linux/macOS
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1

pip install fastapi uvicorn sqlalchemy pydantic psycopg2-binary alembic
        

Module 1 — Introduction & Setup

Learning outcomes: install FastAPI, run first endpoint, explore /docs.

Lab Steps

  1. Create main.py with this code.
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def home():
    return {"message": "Hello FastAPI"}
        

Test

Run: uvicorn main:app --reload → Open http://127.0.0.1:8000/docs

Practice

Create an endpoint /hello/{name} to return student name.

Module 2 — Path & Query Parameters

Outcome: dynamic routes, optional params, validation via type hints.

from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.get("/items/{item_id}")
def get_item(item_id: int):
    return {"item_id": item_id}

@app.get("/search")
def search(q: Optional[str] = None, limit: int = 10):
    return {"q": q, "limit": limit}
        

Practice

Try: /items/5 and /search?q=fastapi&limit=3

Module 3 — Pydantic Models

Outcome: request body validation, correct types & defaults.

from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    description: str | None = None
    in_stock: bool = True

app = FastAPI()

@app.post("/items/")
def create_item(item: Item):
    return item
        

Mini Assignment

Add a field category: str and make it optional.

Module 4 — In-Memory CRUD

Outcome: implement POST/GET/PUT/DELETE against a Python list.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

class Item(BaseModel):
    name: str
    price: float

app = FastAPI()

items_db: List[Item] = []

@app.post("/items/", response_model=Item)
def create_item(item: Item):
    items_db.append(item)
    return item

@app.get("/items/", response_model=List[Item])
def list_items():
    return items_db

@app.get("/items/{item_id}", response_model=Item)
def get_item(item_id: int):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]

@app.put("/items/{item_id}", response_model=Item)
def update_item(item_id: int, updated: Item):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    items_db[item_id] = updated
    return updated

@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    items_db.pop(item_id)
    return {"message":"deleted"}
        

Practice

Use the Swagger UI to create multiple items and update/delete them.

Module 5 — Routers & Project Structure

Outcome: split app using APIRouter, modular code.

Steps

  1. Create routers/items.py with an APIRouter, then include it in main via app.include_router().
Mini assignment: create routers/users.py and a simple register route.

Module 6 — SQLAlchemy ORM & Models

Outcome: create ORM models and connect via SQLAlchemy.

# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "postgresql://postgres:postgres@localhost/fastapi_db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
Base = declarative_base()
        
Mini assignment: add a column rating: int to the model and migrate.

Module 7 — PostgreSQL CRUD (Sync SQLAlchemy)

Outcome: perform real DB CRUD, use session dependency.

# crud.py (example functions)
from sqlalchemy.orm import Session
from . import models, schemas

def create_item(db: Session, item: schemas.ItemCreate):
    db_item = models.Item(**item.dict())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item
        

Test via Swagger UI after running the app connected to Postgres.

Module 8 — Alembic Migrations

Outcome: version DB schema and apply migrations.

pip install alembic
alembic init migrations
# configure env.py (set SQLALCHEMY_URL)
alembic revision --autogenerate -m "init"
alembic upgrade head
        
Mini assignment: add a field then autogenerate migration and apply it.

Module 9 — Dockerize FastAPI

Outcome: build Docker image for FastAPI app.

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn","app.main:app","--host","0.0.0.0","--port","8000"]
        

Module 10 — Docker Compose (FastAPI + Postgres)

version: "3.9"
services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: fastapi_db
    volumes:
      - pgdata:/var/lib/postgresql/data
  api:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db
volumes:
  pgdata:
        

Run: docker-compose up --build

Module 11 — Authentication (JWT Basics)

Outcome: protect endpoints using JWT tokens (login & protected route).

# jwt_utils.py (simplified)
from datetime import datetime, timedelta
from jose import jwt

SECRET_KEY = "change-me"
ALGORITHM = "HS256"

def create_access_token(data: dict, minutes: int = 60):
    to_encode = data.copy()
    to_encode.update({"exp": datetime.utcnow() + timedelta(minutes=minutes)})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
        
Mini assignment: restrict /items/create to authenticated users only.

Module 12 — Deployment (Render / Railway / Docker)

Outcome: prepare app for production, Dockerize, push to GitHub and deploy.

Checklist

  • Secure secrets via environment variables
  • Use Gunicorn/Uvicorn worker for production
  • Use persistent DB (cloud DB or managed Postgres)
Mini assignment: deploy your project to Render (free tier) and share the live URL.

Assessment: MCQs, Short Answers & Coding

MCQs (sample)

  1. FastAPI is built on which spec?
    Answer: OpenAPI
  2. Which command runs FastAPI? uvicorn main:app --reload

Short answer

  1. Explain Alembic and migrations.
  2. Difference between Pydantic schema and SQLAlchemy model.

Coding

  1. Implement /double/{num} that returns double the number.
  2. Create Pydantic model Student(name, age, dept).

Instructor Answer Key (Selected)

Short Answers

Alembic: DB migrations tool that versions schema changes. Use alembic revision --autogenerate.

MCQ Answers

  • FastAPI spec: OpenAPI

Sample Code Solutions

@app.get("/double/{num}")
def double(num: int):
    return {"double": num * 2}
        

Internship-Style Final Project — Library Management API

Requirements: Books CRUD, Users, Borrow records, JWT auth, Dockerized, Deployed. Deliverables: GitHub repo, PDF report, demo video.

Endpoints (minimum)

  • Books: POST/GET/GET/{id}/PUT/DELETE
  • Users: register/login/profile
  • Borrow: POST borrow/GET user borrows

Grading Rubric

ItemMarks
Functionality (CRUD)40
Auth & Security20
Docker + Deployment20
Documentation & Demo20

Export / Print

To create a printable PDF: copy this page's content into Google Docs (paste as plain text), adjust formatting, then File → Download → PDF.

Dockerization of the above Tutorial

FastAPI + PostgreSQL + Docker — Full CRUD Tutorial

πŸš€ FastAPI + PostgreSQL + Docker — Full CRUD Tutorial (Production Ready)

Welcome to the most complete hands-on FastAPI tutorial for students! This guide will show you how to build a real CRUD API using:

  • FastAPI
  • PostgreSQL (real database)
  • SQLAlchemy ORM
  • Alembic migrations
  • Docker & Docker Compose

🐳 Why Docker? (Simple Explanation)

Docker allows you to package your entire FastAPI project and PostgreSQL database in one portable environment.

Benefits for Students:

  • No need to separately install PostgreSQL.
  • No database configuration headaches.
  • The whole app runs using ONE command: docker-compose up --build
  • Everyone in the class runs the same environment.

What Docker Compose Does

ServiceDescription
dbRuns PostgreSQL database in a container
apiRuns FastAPI server inside its own container

This is modern industry practice — and perfect for teaching!

πŸ“ Project Structure


project/
│── app/
│   │── main.py
│   │── database.py
│   │── models.py
│   │── schemas.py
│   │── crud.py
│   │── __init__.py
│── Dockerfile
│── docker-compose.yml
│── requirements.txt

1️⃣ database.py — Database Connection


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "postgresql://postgres:postgres@db:5432/fastapi_db"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)

Base = declarative_base()

2️⃣ models.py — SQLAlchemy ORM Model


from sqlalchemy import Column, Integer, String, Float, Boolean
from .database import Base

class Item(Base):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    price = Column(Float)
    description = Column(String, nullable=True)
    in_stock = Column(Boolean, default=True)

3️⃣ schemas.py — Pydantic Models


from pydantic import BaseModel

class ItemBase(BaseModel):
    name: str
    price: float
    description: str | None = None
    in_stock: bool = True

class ItemCreate(ItemBase):
    pass

class Item(ItemBase):
    id: int
    class Config:
        orm_mode = True

4️⃣ crud.py — All CRUD Operations


from sqlalchemy.orm import Session
from . import models, schemas

def create_item(db: Session, item: schemas.ItemCreate):
    db_item = models.Item(**item.dict())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

def get_items(db: Session):
    return db.query(models.Item).all()

def get_item(db: Session, id: int):
    return db.query(models.Item).filter(models.Item.id == id).first()

def update_item(db: Session, id: int, new_data: schemas.ItemCreate):
    item = db.query(models.Item).filter(models.Item.id == id).first()
    if item:
        for k, v in new_data.dict().items():
            setattr(item, k, v)
        db.commit()
        db.refresh(item)
    return item

def delete_item(db: Session, id: int):
    item = db.query(models.Item).filter(models.Item.id == id).first()
    if item:
        db.delete(item)
        db.commit()
    return item

5️⃣ main.py — FastAPI Routes


from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session

from . import models, schemas, crud
from .database import engine, SessionLocal

# Create tables
models.Base.metadata.create_all(bind=engine)

app = FastAPI(title="FastAPI + Postgres CRUD")

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/items/", response_model=schemas.Item)
def create(item: schemas.ItemCreate, db: Session = Depends(get_db)):
    return crud.create_item(db, item)

@app.get("/items/", response_model=list[schemas.Item])
def read_all(db: Session = Depends(get_db)):
    return crud.get_items(db)

@app.get("/items/{id}", response_model=schemas.Item)
def read_one(id: int, db: Session = Depends(get_db)):
    item = crud.get_item(db, id)
    if not item:
        raise HTTPException(404, "Item not found")
    return item

@app.put("/items/{id}", response_model=schemas.Item)
def update(id: int, item: schemas.ItemCreate, db: Session = Depends(get_db)):
    updated = crud.update_item(db, id, item)
    if not updated:
        raise HTTPException(404, "Item not found")
    return updated

@app.delete("/items/{id}")
def delete(id: int, db: Session = Depends(get_db)):
    deleted = crud.delete_item(db, id)
    if not deleted:
        raise HTTPException(404, "Item not found")
    return {"message": "Deleted successfully"}

🐳 Dockerfile


FROM python:3.11

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY ./app ./app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

🐳 docker-compose.yml


version: "3.9"

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: fastapi_db
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  api:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  pgdata:

πŸ“¦ requirements.txt


fastapi
uvicorn
sqlalchemy
psycopg2-binary
pydantic

▶ How to Run Everything

1️⃣ Build + Start all containers

docker-compose up --build

2️⃣ Open your browser

http://127.0.0.1:8000/docs

✔ Ready for CRUD ✔ Auto docs ✔ Real PostgreSQL ✔ Production ready

Additional CRUD FastAPI - Demo - Real Database

Response Screen Shots: FastAPI + PostgreSQL CRUD Tutorial — Student Lab

FastAPI + PostgreSQL CRUD Tutorial

Complete student lab — code blocks are editable, copyable and downloadable.

Jump to Code

Overview — What students will build

A FastAPI application connected to PostgreSQL with full CRUD (Create, Read, Update, Delete). The tutorial includes project files, installation steps and run commands suitable for VS Code / local lab setup.

Step 1 — Install PostgreSQL

Install & create database fastapi_demo.

Step 2 — Python Dependencies

Install FastAPI, SQLAlchemy and psycopg2.

Step 3 — Project Files

database.py, models.py, schemas.py, main.py.

Step 4 — Run & Test

Run with uvicorn and test at /docs.

Installation & Database Setup

1. Install PostgreSQL

Download from postgresql.org. During install set a password (example uses admin123).

2. Create the database

CREATE DATABASE fastapi_demo;

3. Install Python packages

pip install fastapi uvicorn sqlalchemy psycopg2-binary pydantic

Tip: Use a virtual environment (recommended): python -m venv .venv and activate it before installing.

Project Structure

fastapi_postgres_crud/
├── database.py      # DB connection & session
├── models.py        # SQLAlchemy models
├── schemas.py       # Pydantic schemas
└── main.py          # FastAPI app with CRUD endpoints
        
Open the folder in VS Code. Create the 4 files below and paste the code (copy buttons provided).

Code Files (copy & use)

Click Copy to copy the file contents to clipboard, or Download to save as a file.

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

# Replace username:password@host/dbname as needed
# DATABASE_URL = "postgresql://postgres:admin123@localhost/fastapi_demo"
DATABASE_URL = "postgresql://postgres:admin123@localhost/postgres"  #Skip Step 2 Create Database

# create synchronous engine (SQLAlchemy ORM)
engine = create_engine(DATABASE_URL)

# each request will use a session from this factory
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base = declarative_base()
          

models.py

from sqlalchemy import Column, Integer, String, Float, Boolean
from database import Base

class Item(Base):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, nullable=False)
    price = Column(Float, nullable=False)
    description = Column(String)
    in_stock = Column(Boolean, default=True)
          

schemas.py

from pydantic import BaseModel
from typing import Optional

class ItemBase(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
    in_stock: bool = True

class ItemCreate(ItemBase):
    pass

class Item(ItemBase):
    id: int

    class Config:
        orm_mode = True
          

main.py

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from database import SessionLocal, engine
import models
from models import Item
from schemas import ItemCreate, Item as ItemSchema

# create tables
models.Base.metadata.create_all(bind=engine)

app = FastAPI(title="FastAPI + PostgreSQL CRUD Demo")

# Dependency: DB session per-request
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


# CREATE
@app.post("/items/", response_model=ItemSchema)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
    db_item = Item(**item.dict())
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item


# READ ALL
@app.get("/items/", response_model=list[ItemSchema])
def read_items(db: Session = Depends(get_db)):
    return db.query(Item).all()


# READ ONE
@app.get("/items/{item_id}", response_model=ItemSchema)
def read_item(item_id: int, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if not item:
        raise HTTPException(404, "Item not found")
    return item


# UPDATE
@app.put("/items/{item_id}", response_model=ItemSchema)
def update_item(item_id: int, updated_item: ItemCreate, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if not item:
        raise HTTPException(404, "Item not found")

    for key, value in updated_item.dict().items():
        setattr(item, key, value)

    db.commit()
    db.refresh(item)
    return item


# DELETE
@app.delete("/items/{item_id}")
def delete_item(item_id: int, db: Session = Depends(get_db)):
    item = db.query(Item).filter(Item.id == item_id).first()
    if not item:
        raise HTTPException(404, "Item not found")

    db.delete(item)
    db.commit()

    return {"message": "Deleted successfully", "id": item_id}
          

Run & Test

Run the server

uvicorn main:app --reload

Open Swagger UI

Visit http://127.0.0.1:8000/docs to interact with the API.

If you get a DB connection error, check your DATABASE_URL in database.py and ensure PostgreSQL is running and listening on the host.

Student Exercises (optional)

  1. Add a category column to models.py and update schemas + CRUD.
  2. Add a query param to /items/ to filter by price range.
  3. Implement pagination (limit, offset).
© FastAPI Lab — Paste this HTML into Blogger HTML view. Replace DB creds before publishing.

Additional CRUD FastAPI - Demo - In Memory

FastAPI Full CRUD - Complete Code

FastAPI Full CRUD – Complete Working Code (Copy & Run)

This is the complete, runnable FastAPI project. Your students can copy, paste, save as main.py, and run using uvicorn.

πŸ“Œ main.py (Complete CRUD App)


from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI(title="FastAPI CRUD Demo")


# --------------------------
# 1️⃣ Pydantic Model
# --------------------------
class Item(BaseModel):
    name: str
    price: float
    description: Optional[str] = None
    in_stock: bool = True


# --------------------------
# 2️⃣ In-Memory List DB
# --------------------------
items_db: List[Item] = []


# --------------------------
# 3️⃣ CREATE Item
# --------------------------
@app.post("/items/", response_model=Item)
def create_item(item: Item):
    items_db.append(item)
    return item


# --------------------------
# 4️⃣ READ All Items
# --------------------------
@app.get("/items/", response_model=List[Item])
def read_items():
    return items_db


# --------------------------
# 5️⃣ READ Single Item
# --------------------------
@app.get("/items/{item_id}", response_model=Item)
def read_item(item_id: int):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    return items_db[item_id]


# --------------------------
# 6️⃣ UPDATE Item
# --------------------------
@app.put("/items/{item_id}", response_model=Item)
def update_item(item_id: int, item: Item):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    items_db[item_id] = item
    return item


# --------------------------
# 7️⃣ DELETE Item
# --------------------------
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
    if item_id >= len(items_db):
        raise HTTPException(status_code=404, detail="Item not found")
    deleted = items_db.pop(item_id)
    return {"message": "Item deleted", "deleted_item": deleted}


# Run using:
# uvicorn main:app --reload

πŸš€ How to Run This FastAPI App

1️⃣ Install FastAPI + Uvicorn


pip install fastapi uvicorn

2️⃣ Save the above full code as:

main.py

3️⃣ Run the FastAPI Server


uvicorn main:app --reload

4️⃣ Test in Browser

  • Swagger Docs → http://127.0.0.1:8000/docs
  • Interactive API testing
  • No extra tools needed

5️⃣ Students Can Test CRUD

  • POST → Add item
  • GET → View all
  • GET /items/0 → View first item
  • PUT /items/0 → Update
  • DELETE /items/0 → Delete

πŸŽ‰ Your Full CRUD Demo Is Ready!

This has everything: full code, syntax highlighting, and copy buttons. Paste into Blogger → HTML mode and publish.

Thursday, 20 November 2025

Diagnosis for running all Scripts

 Great — here is the complete ready-to-run folder structure for all 12 FastAPI modules, including:

__init__.py (so Python treats folders as packages)
main.py for every module
✅ A root-level requirements.txt
✅ Windows (.bat) and Linux/Mac (.sh) run scripts
✅ A unified recommended project layout

If you want, I can also fill every module with full working code exactly matching the 12-hour tutorial content.


FASTAPI 12-Module Project — Full Folder Structure

fastapi-12hr/
│
├── requirements.txt
├── run_module.bat
├── run_module.sh
├── README.md
│
├── module1/
│   ├── __init__.py
│   └── main.py
│
├── module2/
│   ├── __init__.py
│   └── main.py
│
├── module3/
│   ├── __init__.py
│   └── main.py
│
├── module4/
│   ├── __init__.py
│   └── main.py
│
├── module5/
│   ├── __init__.py
│   └── main.py
│
├── module6/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   └── db_init.py
│
├── module7/
│   ├── __init__.py
│   ├── main.py
│   ├── auth.py
│   └── users.py
│
├── module8/
│   ├── __init__.py
│   └── main.py
│
├── module9/
│   ├── __init__.py
│   ├── main.py
│   └── test_main.py
│
├── module10/
│   ├── __init__.py
│   └── main.py
│
├── module11/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   ├── schemas.py
│   ├── crud.py
│   └── auth.py
│
└── module12/
    ├── __init__.py
    └── main.py

πŸ“Œ Root Files

requirements.txt

fastapi
uvicorn[standard]
pydantic
sqlmodel
sqlalchemy
aiosqlite
python-jose[bcrypt]
passlib[bcrypt]
pytest
httpx

run_module.bat (Windows)

@echo off
SET MODULE=%1

IF "%MODULE%"=="" (
    echo Usage: run_module module2
    exit /b
)

uvicorn %MODULE%.main:app --reload

Usage:

run_module module2

run_module.sh (Linux / macOS)

#!/bin/bash

MODULE=$1

if [ -z "$MODULE" ]; then
    echo "Usage: ./run_module.sh module2"
    exit 1
fi

uvicorn $MODULE.main:app --reload

Make executable:

chmod +x run_module.sh

Run:

./run_module.sh module2

πŸ“Œ Starter Code for Each Module

Below is minimal working code for every module.
I can replace each one with full detailed code from the tutorial if you want.


module1/main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello, FastAPI Module 1!"}

module2/main.py

(Your original module — fixed)

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    tags: list[str] = []

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

@app.post("/items/")
def create_item(item: Item):
    return {"created": item}

module3/main.py

from fastapi import FastAPI
from pydantic import BaseModel, validator

app = FastAPI()

class User(BaseModel):
    username: str
    age: int

    @validator("age")
    def validate_age(cls, v):
        if v <= 0:
            raise ValueError("Age must be positive")
        return v

@app.post("/user")
def add_user(user: User):
    return user

🧱 I won’t paste all modules unless you request it (to avoid making this message too long).


FASTAPI — 12‑Hour Hands‑On Tutorial (12 Modules) — FULL CODE

This document contains the 12‑module tutorial with full starter code for every module. Each module includes a working main.py and any supporting files required to run the example. Use the root requirements.txt and the provided run_module.sh / run_module.bat to run any module.


Project layout (ready)

fastapi-12hr/
requirements.txt
run_module.sh
run_module.bat
README.md
module1/
__init__.py
main.py
module2/
__init__.py
main.py
module3/
__init__.py
main.py
module4/
__init__.py
main.py
module5/
__init__.py
main.py
module6/
__init__.py
main.py
models.py
db_init.py
module7/
__init__.py
main.py
auth.py
users.py
module8/
__init__.py
main.py
module9/
__init__.py
main.py
test_main.py
module10/
__init__.py
main.py
module11/
__init__.py
main.py
models.py
schemas.py
crud.py
auth.py
module12/
__init__.py
main.py

requirements.txt

fastapi
uvicorn[standard]
pydantic
sqlmodel
sqlalchemy
aiosqlite
python-jose[bcrypt]
passlib[bcrypt]
pytest
httpx

run_module.sh

#!/bin/bash
MODULE=$1
if [ -z "$MODULE" ]; then
echo "Usage: ./run_module.sh module2"
exit 1
fi
uvicorn $MODULE.main:app --reload

Make executable: chmod +x run_module.sh


run_module.bat

@echo off
SET MODULE=%1
IF "%MODULE%"=="" (
echo Usage: run_module module2
exit /b
)
uvicorn %MODULE%.main:app --reload

Module code (full)

Note: For each module you can run ./run_module.sh moduleX (or run_module moduleX on Windows) from the repository root.


module1 — Basics

module1/main.py

from fastapi import FastAPI

app = FastAPI(
title="Module 1 - FastAPI Basics",
description="Simple hello world",
version="0.1",
)

@app.get("/", summary="Root endpoint")
def read_root():
return {"message": "Hello, FastAPI Module 1!"}

module2 — Path params, query params, request body

module2/main.py

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI(title="Module 2 - Params & Body")

class Item(BaseModel):
name: str
price: float
tags: List[str] = []

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
"""Path param `item_id` and optional query param `q`"""
return {"item_id": item_id, "q": q}

@app.post("/items/")
def create_item(item: Item):
"""Receive JSON body validated by Pydantic"""
# Returning `item` directly will be serialized to JSON
return {"created": item}

module3 — Data validation with Pydantic

module3/main.py

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr, validator
from typing import List

app = FastAPI(title="Module 3 - Pydantic")

class User(BaseModel):
username: str
email: EmailStr
age: int

@validator("age")
def age_must_be_positive(cls, v):
if v < 0:
raise ValueError("age must be positive")
return v

class Group(BaseModel):
name: str
members: List[User]

@app.post("/groups/")
def create_group(group: Group):
return {"group": group}

module4 — Dependency injection & simple API key security

module4/main.py

from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional

app = FastAPI(title="Module 4 - Dependencies & Security")

async def get_api_key(x_api_key: Optional[str] = Header(None)):
if x_api_key != "secret123":
raise HTTPException(status_code=401, detail="Unauthorized")
return x_api_key

@app.get("/secure")
async def secure_endpoint(api_key: str = Depends(get_api_key)):
return {"message": "Secure data", "api_key": api_key}

# Example of injecting a simple resource (like DB session placeholder)
async def get_db():
# yield a fake resource; replace with real DB session in later modules
yield {"db": "fake-session"}

@app.get("/use-db")
async def use_db(db = Depends(get_db)):
return {"using": db}

module5 — Background tasks, startup/shutdown events, middleware

module5/main.py

from fastapi import FastAPI, BackgroundTasks, Request
import time

app = FastAPI(title="Module 5 - Background & Middleware")

@app.on_event("startup")
def startup():
print("[startup] App is starting")

@app.on_event("shutdown")
def shutdown():
print("[shutdown] App is shutting down")

def write_log(message: str):
with open("module5.log", "a", encoding="utf-8") as f:
f.write(message + "
")

@app.post("/notify")
def notify(background_tasks: BackgroundTasks, msg: str):
background_tasks.add_task(write_log, msg)
return {"status": "scheduled"}

# Simple middleware to log path and duration
@app.middleware("http")
async def log_middleware(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
with open("module5_requests.log", "a", encoding="utf-8") as f:
f.write(f"{request.method} {request.url.path} {duration:.4f}s
")
return response

module6 — Database integration (SQLModel + SQLite)

module6/models.py

from sqlmodel import SQLModel, Field
from typing import Optional

class Todo(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
title: str
done: bool = False

module6/db_init.py

from sqlmodel import create_engine, SQLModel
from .models import Todo

sqlite_url = "sqlite:///./module6_todos.db"
engine = create_engine(sqlite_url, echo=False)

def init_db():
SQLModel.metadata.create_all(engine)

module6/main.py

from fastapi import FastAPI, HTTPException
from sqlmodel import Session, select
from .models import Todo
from .db_init import engine, init_db

app = FastAPI(title="Module 6 - SQLModel (SQLite)")
init_db()

@app.post('/todos/')
def create_todo(todo: Todo):
with Session(engine) as session:
session.add(todo)
session.commit()
session.refresh(todo)
return todo

@app.get('/todos/')
def list_todos():
with Session(engine) as session:
todos = session.exec(select(Todo)).all()
return todos

@app.get('/todos/{todo_id}')
def get_todo(todo_id: int):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail='Not found')
return todo

@app.put('/todos/{todo_id}')
def update_todo(todo_id: int, updated: Todo):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail='Not found')
todo.title = updated.title
todo.done = updated.done
session.add(todo)
session.commit()
session.refresh(todo)
return todo

@app.delete('/todos/{todo_id}')
def delete_todo(todo_id: int):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail='Not found')
session.delete(todo)
session.commit()
return {"deleted": todo_id}

module7 — Authentication (OAuth2 password flow + JWT sketch)

module7/users.py

# simple in-memory user store (for demo only)
users_db = {
"alice": {
"username": "alice",
"full_name": "Alice Example",
"email": "alice@example.com",
"hashed_password": "$2b$12$KIX0...", # replace with real bcrypt hash
"disabled": False,
}
}

module7/auth.py

from datetime import datetime, timedelta
from jose import jwt
from passlib.context import CryptContext

SECRET_KEY = "CHANGE_THIS_SECRET"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

module7/main.py

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from .users import users_db
from .auth import verify_password, create_access_token
from datetime import timedelta

app = FastAPI(title="Module 7 - Auth (JWT)")

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

@app.post('/token')
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = users_db.get(form_data.username)
if not user:
raise HTTPException(status_code=400, detail='Incorrect username or password')
# NOTE: hashed_password should be real bcrypt hash; for demo assume verify_password returns True
if not verify_password(form_data.password, user.get('hashed_password', '')):
raise HTTPException(status_code=400, detail='Incorrect username or password')
access_token_expires = timedelta(minutes=30)
access_token = create_access_token(data={"sub": user['username']}, expires_delta=access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}

@app.get('/protected')
def protected_route(token: str = Depends(oauth2_scheme)):
return {"token": token}

Note: This module uses a simplified in-memory store and a placeholder bcrypt hash. For real apps, use a DB and properly hash passwords.


module8 — File uploads & streaming


FAST API - Intro

 https://fastapi-hha68n1.gamma.site/