A RESTful API built with FastAPI for managing tasks with user authentication, PostgreSQL database, Redis caching, and OpenTelemetry observability.
- User authentication with JWT tokens
- CRUD operations for tasks
- PostgreSQL database with SQLAlchemy ORM
- Redis caching for improved performance
- Password hashing with bcrypt
- Token-based authentication
- Pagination support for task listing
- Docker support for easy deployment
- Command Line Interface (CLI) for easy interaction
- Type checking support with mypy
- OpenTelemetry integration with Jaeger for distributed tracing
The API uses the following configuration settings:
API_V1_STR
: "/api/v1" (API version prefix)PROJECT_NAME
: "Task Management API"SECRET_KEY
: JWT secret key (set in .env)ALGORITHM
: "HS256" (JWT encryption algorithm)ACCESS_TOKEN_EXPIRE_MINUTES
: 30 (JWT token expiration time)
POSTGRES_USER
: Database usernamePOSTGRES_PASSWORD
: Database passwordPOSTGRES_HOST
: Database hostPOSTGRES_PORT
: Database port (default: 5432)POSTGRES_DB
: Database name
REDIS_HOST
: Redis host (default: redis)REDIS_PORT
: Redis port (default: 6379)CACHE_EXPIRE_IN_SECONDS
: Cache TTL in seconds (default: 60)
OTLP_ENDPOINT
: OpenTelemetry collector endpoint (default: http://jaeger:4317)ENVIRONMENT
: Deployment environment (default: development)
- Python 3.12+
- FastAPI
- SQLAlchemy
- Python-Jose
- Passlib
- uvicorn
- Redis
- OpenTelemetry SDK and instrumentations
- Type stubs for development:
- types-python-jose
- types-passlib
- types-redis
- mypy for static type checking
- black for code formatting
- isort for import sorting
- flake8 for code linting
- pytest for testing
- pytest-asyncio for async testing support
- pytest-cov for test coverage
- Docker
- Docker Compose
- Clone the repository:
git clone <repository-url>
cd task_api
- Create a
.env
file in the project root with the following variables:
# API Settings
SECRET_KEY=your-secret-key-here
# PostgreSQL Settings
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_password
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DB=taskdb
# Redis Settings (optional - defaults will be used if not set)
REDIS_HOST=redis
REDIS_PORT=6379
CACHE_EXPIRE_IN_SECONDS=60
# OpenTelemetry Settings
OTLP_ENDPOINT=http://jaeger:4317
ENVIRONMENT=development
- Build and start the containers:
# Clean up any existing containers and volumes
docker-compose down --volumes --remove-orphans
# Build and start services
docker-compose up -d --build
The API will be available at http://localhost:8000 The Jaeger UI will be available at http://localhost:16686
The application uses OpenTelemetry for distributed tracing, providing insights into:
- HTTP requests and responses
- Database queries
- Redis operations
- Service dependencies
- Cache operations (including cache hits)
- Open the Jaeger UI at http://localhost:16686
- Select "Task Management API" from the Service dropdown
- Click "Find Traces" to view recent traces
- HTTP request details (method, path, status code)
- Database query execution time
- Redis operations timing
- Cache operations (hits and misses)
- Service dependencies and interactions
- Error details when failures occur
- Individual endpoint spans with custom names
- FastAPI (HTTP requests/responses)
- SQLAlchemy (database operations)
- Note: For async SQLAlchemy engines, instrumentation is applied to the underlying sync_engine
- Redis (cache operations)
- Custom cache operations with traced_cache decorator
The application uses a custom traced_cache
decorator that wraps the standard FastAPI Cache decorator to ensure that:
- Cache hits are properly traced and visible in Jaeger
- Cache operations include relevant attributes (key, hit/miss status)
- User context is maintained in cached responses
- Performance metrics for cache operations are collected
This ensures complete observability even when responses are served from cache.
The application uses the W3C Trace Context standard for propagating trace context between services. This ensures that:
- Distributed traces are properly connected
- Context is maintained across service boundaries
- Trace IDs are consistent throughout the request lifecycle
In addition to automatic instrumentation, the application uses manual instrumentation for:
- Endpoint handlers (with named spans for each operation)
- Authentication flows
- Database operations
- Cache operations
This provides more detailed and meaningful traces in the Jaeger UI.
The project uses several development tools that can be installed via requirements-dev.txt
:
- mypy: Static type checking
- black: Code formatting
- isort: Import sorting
- flake8: Code linting
- pytest: Testing framework
- pytest-asyncio: Async testing support
- pytest-cov: Test coverage
# Format code
black app/
isort app/
# Check types
mypy app/
# Lint code
flake8 app/
# Run tests
pytest
# Run tests with coverage
pytest --cov=app
The application consists of four services:
-
api
: The FastAPI application- Builds from the Dockerfile
- Exposes port 8000
- Depends on database, cache, and telemetry services
-
db
: PostgreSQL database- Uses postgres:latest image
- Persistent volume for data storage
- Includes health checks
-
redis
: Redis cache- Uses redis:alpine image
- Persistent volume for data
- Includes health checks
- Improves API performance through caching
-
jaeger
: Jaeger tracing- Uses jaegertracing/all-in-one image
- Collects and visualizes distributed traces
- Exposes UI on port 16686
- Receives traces via OTLP on port 4317
Start services:
docker-compose up -d
View logs:
docker-compose logs -f
Stop services:
docker-compose down
Clean restart:
docker-compose down --volumes --remove-orphans
docker-compose up -d --build
Access PostgreSQL CLI:
docker-compose exec db psql -U <POSTGRES_USER> -d <POSTGRES_DB>
Access Redis CLI:
docker-compose exec redis redis-cli
POST /token
- Get access tokenPOST /users
- Create new user
GET /tasks
- List all tasks (paginated, cached)GET /tasks/{task_id}
- Get specific task (cached)POST /tasks
- Create new taskPUT /tasks/{task_id}
- Update taskDELETE /tasks/{task_id}
- Delete task
curl -X 'POST' \
'http://localhost:8000/users' \
-H 'Content-Type: application/json' \
-d '{
"username": "testuser",
"password": "testpass"
}'
curl -X 'POST' \
'http://localhost:8000/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=testuser&password=testpass'
curl -X 'POST' \
'http://localhost:8000/tasks' \
-H 'Authorization: Bearer <your-token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "Test Task",
"description": "Task description",
"status": "pending"
}'
Tasks can have one of three states:
pending
: Task is waiting to be startedin_progress
: Task is currently being worked ondone
: Task is completed
Example of creating a task with status:
curl -X 'POST' \
'http://localhost:8000/tasks' \
-H 'Authorization: Bearer <your-token>' \
-H 'Content-Type: application/json' \
-d '{
"title": "Test Task",
"description": "Task description",
"status": "pending"
}'
- id (Integer, Primary Key)
- username (String, Unique)
- hashed_password (String)
- id (Integer, Primary Key)
- title (String)
- description (String)
- status (String, one of: "pending", "in_progress", "done")
- created_at (DateTime)
- updated_at (DateTime)
- owner_id (Integer, Foreign Key to users.id)
The API uses Redis for caching with the following features:
- Task listing and individual task retrieval are cached
- Default cache TTL: 60 seconds
- Automatic cache invalidation on task updates/deletes
- Configurable cache settings via environment variables
- Passwords are hashed using bcrypt
- Authentication uses JWT tokens
- Token expiration set to 30 minutes
- Protected endpoints require valid JWT token
- CORS middleware configured for security
- Rate limiting to protect against abuse (all configurable via environment variables):
- Default rate limit: 60 requests per minute
- Login: 10 requests per minute
- User creation: 5 requests per minute
- Task creation: 30 requests per minute
- Task listing: 60 requests per minute
- Task retrieval: 120 requests per minute
The project uses:
- FastAPI for the web framework
- SQLAlchemy for ORM
- Pydantic for data validation
- JWT for authentication
- Redis for caching
- Docker for containerization
- mypy for static type checking
- OpenTelemetry for distributed tracing
The project supports static type checking using mypy. To run type checking:
# Inside Docker container
docker-compose exec api mypy app/
# Or locally
mypy app/
All code is type-annotated and verified with mypy to ensure type safety and catch potential type-related issues early in development.
The project includes a Go-based CLI tool for interacting with the API.
cd cli
go build -o task-cli
login
- Authenticate with the API# Interactive (secure) mode - will prompt for password task-cli login -u username # Non-interactive mode task-cli login -u username -p password
logout
- Clear stored authenticationtask-cli logout
create
- Create a new tasktask-cli create -t "Task Title" -d "Task Description" -s pending
list
- List all tasks# Default format task-cli list # With pagination task-cli list -p 1 -l 20 # JSON output task-cli list --json # or task-cli list -j
get
- Get a specific task# Default format task-cli get -i task_id # JSON output task-cli get -i task_id --json # or task-cli get -i task_id -j
update
- Update an existing tasktask-cli update -i task_id -t "New Title" -d "New Description" -s in_progress
delete
- Delete a tasktask-cli delete -i task_id
The CLI stores its configuration in ~/.task-cli.json
with the following settings:
api_base_url
: API endpoint (default: "http://localhost:8000")access_token
: JWT token for authentication (managed automatically by login/logout commands)
This project is licensed under the Apache License 2.0 - see the LICENSE
file for details.