This Task API follows best practices for FastAPI applications, implementing a clean repository pattern with comprehensive dependency injection.
┌─────────────────────────────────────┐
│ API Layer (routes.py) │ ← FastAPI endpoints
├─────────────────────────────────────┤
│ Service Layer (service.py) │ ← Business logic
├─────────────────────────────────────┤
│ Repository Layer (repository.py) │ ← Data access
├─────────────────────────────────────┤
│ Database Layer (database.py) │ ← SQLModel/SQLite
└─────────────────────────────────────┘
- Pydantic models for request/response validation
TodoCreate,TodoUpdate,TodoResponse- Type-safe data transfer objects
- SQLModel database models
Todoentity with SQLAlchemy mappings
- Abstract repository interface (
AbstractTodoRepository) - Concrete implementation (
TodoRepository) - Encapsulates all database operations
- Follows repository pattern for testability
- Business logic layer (
TodoService) - Uses repository through dependency injection
- Converts between entities and schemas
- FastAPI endpoint definitions
- Uses service through dependency injection
- HTTP status codes and error handling
- FastAPI dependency injection setup
- Type-annotated dependencies
- Automatic dependency resolution
- Database connection and session management
- Table creation on startup
- Session factory for dependency injection
# 1. Database Session
SessionDep = Annotated[Session, Depends(get_session)]
# 2. Repository (depends on Session)
RepositoryDep = Annotated[TaskRepository, Depends(get_task_repository)]
# 3. Service (depends on Repository)
ServiceDep = Annotated[TaskService, Depends(get_task_service)]
# 4. Route (depends on Service)
@router.post("")
def create_task(task_data: TaskCreate, service: ServiceDep):
return service.create_task(task_data)GET /- Health checkGET /readyz- Readiness checkGET /livez- Liveness check
POST /todos- Create a new todoGET /todos- List all todos (with filtering & pagination)GET /todos/{id}- Get a specific todoPUT /todos/{id}- Update a todoDELETE /todos/{id}- Delete a todoPOST /todos/{id}/complete- Mark todo as completePOST /todos/{id}/incomplete- Mark todo as incomplete
# Using Python module
python -m src.example_app.main
# Or with uvicorn directly
uvicorn example_app.main:app --reloadmake runOnce running, visit:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
curl -X POST "http://localhost:8000/todos" \
-H "Content-Type: application/json" \
-d '{
"title": "Buy groceries",
"description": "Milk, eggs, bread",
"completed": false
}'# All todos
curl "http://localhost:8000/todos"
# Only completed
curl "http://localhost:8000/todos?completed=true"
# With pagination
curl "http://localhost:8000/todos?skip=0&limit=10"curl -X PUT "http://localhost:8000/todos/1" \
-H "Content-Type: application/json" \
-d '{
"title": "Buy groceries - Updated",
"completed": true
}'curl -X POST "http://localhost:8000/todos/1/complete"curl -X DELETE "http://localhost:8000/todos/1"# Run all tests
pytest
# With coverage
pytest --cov=src
# Specific test file
pytest tests/test_app.py -v- All dependencies injected through FastAPI's DI system
- Easy to mock for testing
- Loose coupling between layers
- Abstract interface for data access
- Easy to swap implementations (SQLite → PostgreSQL)
- Testable without database
- Each layer has single responsibility
- Business logic in service layer
- Data access in repository layer
- HTTP handling in routes layer
- Full type hints throughout
- Pydantic validation
- SQLModel for type-safe queries
- Proper HTTP status codes
- Descriptive error messages
- 404 for not found resources
- RESTful conventions
- Pagination support
- Filtering capabilities
- Proper HTTP methods
- Comprehensive test coverage
- Dependency override for testing
- In-memory SQLite for tests
- Test client for API testing
src/example_app/
├── __init__.py
├── main.py # Application entry point
├── routes.py # API endpoints
├── service.py # Business logic
├── repository.py # Data access layer
├── dependencies.py # DI configuration
├── database.py # Database setup
├── entities.py # SQLModel entities
├── schemas.py # Pydantic schemas
├── models.py # Utility models
├── settings.py # Configuration
└── logger.py # Logging setup
tests/
├── test_app.py # API tests
└── test_settings.py # Settings tests
- Maintainability: Clear separation makes code easy to understand and modify
- Testability: Each layer can be tested independently
- Scalability: Easy to add new features or swap implementations
- Type Safety: Catches errors at development time
- Reusability: Service and repository layers can be reused
- Flexibility: Easy to switch databases or add caching