This repository focuses on mastering API development using Python frameworks to build, manage, and automate RESTful APIs for Internal Development Platform (IDP) development.
By working through this repository, you will:
- Master FastAPI and Flask for API development
- Implement RESTful API best practices
- Work with API authentication and authorization
- Build GraphQL APIs with Python
- Implement API documentation with OpenAPI/Swagger
- Configure API gateways and rate limiting
- Deploy APIs to production environments
- Python 3.11 or higher
- Basic understanding of HTTP and REST principles
- Completed learning-idp-python-azure-sdk
- Completed learning-idp-test-driven-development
- Git and GitHub account
learning-idp-api-development/
βββ README.md # This file
βββ REFERENCES.md # Links to resources and related repos
βββ pyproject.toml # Python project configuration
βββ requirements.txt # Python dependencies
βββ requirements-dev.txt # Development dependencies
βββ .python-version # Python version for pyenv
βββ .gitignore # Git ignore patterns
βββ .env.example # Environment variables template
β
βββ docs/
β βββ concepts/
β β βββ 01-api-overview.md
β β βββ 02-rest-principles.md
β β βββ 03-api-design.md
β β βββ 04-authentication.md
β β βββ 05-api-versioning.md
β β βββ 06-api-security.md
β βββ guides/
β β βββ getting-started.md
β β βββ fastapi-setup.md
β β βββ authentication-oauth2.md
β β βββ api-documentation.md
β β βββ deployment.md
β βββ examples/
β βββ simple-rest-api.md
β βββ crud-operations.md
β βββ authentication-flow.md
β βββ graphql-api.md
β βββ microservices-api.md
β
βββ src/
β βββ __init__.py
β β
β βββ core/
β β βββ __init__.py
β β βββ config.py # Configuration management
β β βββ exceptions.py # Custom exceptions
β β βββ logging_config.py # Logging setup
β β βββ dependencies.py # FastAPI dependencies
β β
β βββ api/
β β βββ __init__.py
β β βββ main.py # FastAPI application
β β βββ routes/
β β β βββ __init__.py
β β β βββ health.py # Health check endpoint
β β β βββ users.py # User endpoints
β β β βββ auth.py # Authentication endpoints
β β β βββ items.py # Resource endpoints
β β βββ middleware/
β β βββ __init__.py
β β βββ cors.py # CORS middleware
β β βββ auth.py # Auth middleware
β β βββ rate_limit.py # Rate limiting
β β βββ logging.py # Request logging
β β
β βββ models/
β β βββ __init__.py
β β βββ user.py # User model
β β βββ item.py # Item model
β β βββ auth.py # Auth models
β β βββ base.py # Base model
β β
β βββ schemas/
β β βββ __init__.py
β β βββ user.py # User schemas (Pydantic)
β β βββ item.py # Item schemas
β β βββ auth.py # Auth schemas
β β βββ common.py # Common schemas
β β
β βββ services/
β β βββ __init__.py
β β βββ user_service.py # User business logic
β β βββ auth_service.py # Authentication logic
β β βββ item_service.py # Item business logic
β β βββ cache_service.py # Caching service
β β
β βββ repositories/
β β βββ __init__.py
β β βββ user_repository.py # User data access
β β βββ item_repository.py # Item data access
β β βββ base_repository.py # Base repository
β β
β βββ auth/
β β βββ __init__.py
β β βββ jwt.py # JWT token handling
β β βββ oauth2.py # OAuth2 implementation
β β βββ password.py # Password hashing
β β βββ permissions.py # Permission system
β β
β βββ database/
β β βββ __init__.py
β β βββ session.py # Database session
β β βββ base.py # Base database class
β β βββ migrations/ # Alembic migrations
β β βββ versions/
β β
β βββ graphql/
β β βββ __init__.py
β β βββ schema.py # GraphQL schema
β β βββ queries.py # GraphQL queries
β β βββ mutations.py # GraphQL mutations
β β βββ resolvers.py # Field resolvers
β β
β βββ utils/
β βββ __init__.py
β βββ validators.py # Input validators
β βββ serializers.py # Data serializers
β βββ pagination.py # Pagination helpers
β βββ rate_limiter.py # Rate limiting utilities
β
βββ examples/
β βββ 01_fastapi_basics/
β β βββ 01_hello_world.py
β β βββ 02_path_parameters.py
β β βββ 03_query_parameters.py
β β βββ 04_request_body.py
β β βββ 05_response_model.py
β β
β βββ 02_crud_operations/
β β βββ 01_create_user.py
β β βββ 02_read_user.py
β β βββ 03_update_user.py
β β βββ 04_delete_user.py
β β βββ 05_list_users.py
β β
β βββ 03_authentication/
β β βββ 01_basic_auth.py
β β βββ 02_jwt_auth.py
β β βββ 03_oauth2_flow.py
β β βββ 04_refresh_tokens.py
β β βββ 05_permissions.py
β β
β βββ 04_database_integration/
β β βββ 01_sqlalchemy_setup.py
β β βββ 02_models.py
β β βββ 03_relationships.py
β β βββ 04_queries.py
β β βββ 05_migrations.py
β β
β βββ 05_advanced_features/
β β βββ 01_file_upload.py
β β βββ 02_background_tasks.py
β β βββ 03_websockets.py
β β βββ 04_streaming.py
β β βββ 05_caching.py
β β
β βββ 06_graphql/
β β βββ 01_basic_schema.py
β β βββ 02_queries.py
β β βββ 03_mutations.py
β β βββ 04_subscriptions.py
β β βββ 05_dataloader.py
β β
β βββ 07_testing/
β β βββ 01_test_endpoints.py
β β βββ 02_test_auth.py
β β βββ 03_test_database.py
β β βββ 04_test_integration.py
β β βββ 05_test_performance.py
β β
β βββ 08_deployment/
β βββ 01_docker_deployment/
β β βββ Dockerfile
β β βββ docker-compose.yml
β βββ 02_kubernetes_deployment/
β β βββ deployment.yaml
β β βββ service.yaml
β βββ 03_azure_deployment/
β βββ deploy_to_azure.py
β
βββ templates/
β βββ api/
β β βββ basic_rest_api.py
β β βββ crud_api.py
β β βββ microservice_api.py
β βββ schemas/
β β βββ user_schema.py
β β βββ auth_schema.py
β β βββ error_schema.py
β βββ middleware/
β βββ auth_middleware.py
β βββ cors_middleware.py
β βββ rate_limit_middleware.py
β
βββ notebooks/
β βββ 01_api_basics.ipynb
β βββ 02_authentication.ipynb
β βββ 03_database_integration.ipynb
β βββ 04_graphql_api.ipynb
β βββ 05_api_testing.ipynb
β
βββ scripts/
β βββ create_api_project.sh # Project setup script
β βββ run_dev_server.sh # Development server
β βββ generate_openapi.py # Generate OpenAPI spec
β βββ migrate_database.sh # Run migrations
β
βββ tests/
β βββ __init__.py
β βββ conftest.py
β βββ unit/
β β βββ test_models.py
β β βββ test_schemas.py
β β βββ test_services.py
β β βββ test_auth.py
β βββ integration/
β β βββ test_api_endpoints.py
β β βββ test_authentication.py
β β βββ test_database.py
β β βββ test_webhooks.py
β βββ e2e/
β βββ test_user_flow.py
β βββ test_api_flow.py
β
βββ .github/
βββ workflows/
βββ api-test.yml # API testing
βββ api-deploy.yml # Deployment
βββ api-docs.yml # Documentation generation
git clone https://github.com/vanHeemstraSystems/learning-idp-api-development.git
cd learning-idp-api-development# Create virtual environment
python3 -m venv venv
# Activate virtual environment
# On Linux/MacOS:
source venv/bin/activate
# On Windows:
# venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-dev.txt# Run the basic FastAPI example
cd examples/01_fastapi_basics
uvicorn 01_hello_world:app --reload
# Test it
curl http://localhost:8000
curl http://localhost:8000/docs # Swagger UI# Run the CRUD example
python examples/02_crud_operations/01_create_user.py
# Access the API
curl -X POST http://localhost:8000/users \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "[email protected]"}'Follow this recommended sequence:
Day 1-2: FastAPI Basics
- Read
docs/concepts/01-api-overview.md - Complete examples in
examples/01_fastapi_basics/ - Practice path and query parameters
Day 3-4: CRUD Operations
- Study
docs/concepts/02-rest-principles.md - Work through
examples/02_crud_operations/ - Implement full CRUD API
Day 5-7: Database Integration
- Complete examples in
examples/04_database_integration/ - Implement SQLAlchemy models
- Practice database migrations
Day 1-3: Authentication
- Read
docs/concepts/04-authentication.md - Complete examples in
examples/03_authentication/ - Implement JWT authentication
Day 4-7: API Security
- Study
docs/concepts/06-api-security.md - Implement OAuth2 flows
- Configure CORS and rate limiting
Day 1-3: Advanced API Features
- Work through
examples/05_advanced_features/ - Implement file uploads
- Configure background tasks
Day 4-7: GraphQL API
- Complete examples in
examples/06_graphql/ - Build GraphQL schema
- Implement queries and mutations
Day 1-3: API Testing
- Study testing best practices
- Work through
examples/07_testing/ - Implement integration tests
Day 4-7: Deployment
- Complete examples in
examples/08_deployment/ - Deploy to containers
- Deploy to Azure
# FastAPI Stack
fastapi>=0.109.0 # Modern API framework
uvicorn[standard]>=0.27.0 # ASGI server
pydantic>=2.5.0 # Data validation
python-multipart>=0.0.6 # Form data support
# Flask Alternative
flask>=3.0.0 # Flask framework
flask-restful>=0.3.10 # REST extensions
flask-smorest>=0.44.0 # OpenAPI integration
# GraphQL
strawberry-graphql>=0.219.0 # GraphQL library
graphene>=3.3 # GraphQL frameworksqlalchemy>=2.0.0 # SQL toolkit and ORM
alembic>=1.13.0 # Database migrations
asyncpg>=0.29.0 # Async PostgreSQL
databases>=0.8.0 # Async database supportpython-jose[cryptography]>=3.3.0 # JWT tokens
passlib[bcrypt]>=1.7.4 # Password hashing
python-oauth2>=1.1.1 # OAuth2 implementationfrom fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
from typing import List
app = FastAPI(
title="My API",
description="API for learning",
version="1.0.0"
)
# Pydantic models
class User(BaseModel):
id: int | None = None
name: str
email: EmailStr
is_active: bool = True
# In-memory storage
users_db: List[User] = []
@app.get("/")
async def root():
return {"message": "Welcome to my API"}
@app.post("/users", response_model=User, status_code=201)
async def create_user(user: User):
user.id = len(users_db) + 1
users_db.append(user)
return user
@app.get("/users", response_model=List[User])
async def list_users():
return users_db
@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: int):
for user in users_db:
if user.id == user_id:
return user
raise HTTPException(status_code=404, detail="User not found")
@app.put("/users/{user_id}", response_model=User)
async def update_user(user_id: int, updated_user: User):
for idx, user in enumerate(users_db):
if user.id == user_id:
updated_user.id = user_id
users_db[idx] = updated_user
return updated_user
raise HTTPException(status_code=404, detail="User not found")
@app.delete("/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
for idx, user in enumerate(users_db):
if user.id == user_id:
users_db.pop(idx)
return
raise HTTPException(status_code=404, detail="User not found")from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
# Configuration
SECRET_KEY = "your-secret-key-here"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Password utilities
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
# Token utilities
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
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# Get user from database
user = get_user_from_db(username)
if user is None:
raise credentials_exception
return user
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_userfrom sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from fastapi import Depends
# Database setup
DATABASE_URL = "postgresql://user:password@localhost/dbname"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Database models
class UserDB(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
# Create tables
Base.metadata.create_all(bind=engine)
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# API endpoints with database
@app.post("/users", response_model=User)
async def create_user_db(user: UserCreate, db: Session = Depends(get_db)):
db_user = UserDB(
name=user.name,
email=user.email,
hashed_password=get_password_hash(user.password)
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@app.get("/users/{user_id}", response_model=User)
async def get_user_db(user_id: int, db: Session = Depends(get_db)):
db_user = db.query(UserDB).filter(UserDB.id == user_id).first()
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_userimport strawberry
from typing import List
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class User:
id: int
name: str
email: str
is_active: bool
@strawberry.type
class Query:
@strawberry.field
def user(self, id: int) -> User | None:
# Get user from database
return get_user_from_db(id)
@strawberry.field
def users(self) -> List[User]:
# Get all users from database
return get_all_users_from_db()
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, name: str, email: str) -> User:
# Create user in database
return create_user_in_db(name, email)
@strawberry.mutation
def update_user(self, id: int, name: str | None = None,
email: str | None = None) -> User:
# Update user in database
return update_user_in_db(id, name, email)
schema = strawberry.Schema(query=Query, mutation=Mutation)
# Add to FastAPI
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.get("/limited")
@limiter.limit("5/minute")
async def limited_endpoint(request: Request):
return {"message": "This endpoint is rate limited"}from fastapi import APIRouter
# Version 1
router_v1 = APIRouter(prefix="/api/v1")
@router_v1.get("/users")
async def get_users_v1():
return {"version": "1.0", "users": []}
# Version 2
router_v2 = APIRouter(prefix="/api/v2")
@router_v2.get("/users")
async def get_users_v2():
return {"version": "2.0", "users": [], "metadata": {}}
app.include_router(router_v1)
app.include_router(router_v2)from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
class APIError(Exception):
def __init__(self, status_code: int, detail: str):
self.status_code = status_code
self.detail = detail
@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.detail}
)from pydantic import BaseModel, validator, EmailStr
class UserCreate(BaseModel):
name: str
email: EmailStr
password: str
@validator('name')
def name_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('name cannot be empty')
return v
@validator('password')
def password_strength(cls, v):
if len(v) < 8:
raise ValueError('password must be at least 8 characters')
return vfrom pydantic import BaseModel
class UserResponse(BaseModel):
id: int
name: str
email: str
class Config:
from_attributes = True # For SQLAlchemy models
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
return get_user_from_db(user_id)- learning-internal-development-platform - Main overview
- learning-idp-python-azure-sdk - Azure SDK fundamentals
- learning-idp-test-driven-development - API testing
- learning-idp-containerization - API containerization
- learning-idp-observability - API monitoring
This is a personal learning repository, but suggestions and improvements are welcome!
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure all tests pass
- Submit a pull request
This project is for educational purposes. See LICENSE file for details.
Willem van Heemstra
- GitHub: @vanHeemstraSystems
- LinkedIn: Willem van Heemstra
Last updated: December 18, 2025 Part of the learning-internal-development-platform series