This is a base project for a FastAPI application, currently only have feature user authentication.
The project is structured as follows:
/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app entry point
│ ├── settings.py # FastAPI centralized settings
│ ├── database.py # Database connection setup
│ ├── core/
│ │ └── logger.py # Centralized logging utility
│ ├── users/
│ │ ├── __init__.py
│ │ ├── models.py # Pydantic models
│ │ ├── routers.py # API Endpoint/handler
│ │ ├── services.py # Service or main business logic
│ │ └── storage.py # Raw query
│ ├── dependencies/
│ │ ├── auth.py # FastAPI dependencies for authentication
│ │ └── logger.py # Logger dependency injection
│ └── middleware/
│ ├── logging.py # Logging middleware
│ └── trace_id.py # Trace ID middleware
├── schema/
│ └── 001_create_users.sql # Database schema files
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Test configuration
│ ├── test_services.py # Service layer tests
│ ├── test_storage.py # Storage layer tests
│ └── test_routers.py # Router/Handler integration tests
├── .gitignore
├── pyproject.toml
├── pytest.ini
├── README.md
└── uv.lock
-
Clone this repo
-
Install uv package manager
-
Install dependencies:
uv sync
-
Set up the database URL environment variable. For local development, you can use:
export DATABASE_URL="postgresql://postgres:localdb123@localhost/fast_db"
Alternatively, you can create a
.envfile and the application will load it.Example
.envfile:DATABASE_URL="postgresql://postgres:localdb123@localhost/fast_db" TEST_DATABASE_URL="postgresql://postgres:localdb123@localhost/test_db_1" -
Run the test:
uv run pytest -
Run the application:
uv run uvicorn app.main:app --reload
This project uses a centralized logging utility (app/core/logger.py) and FastAPI's dependency injection system (app/dependencies/logger.py) to ensure consistent log formatting and easier debugging. The AppLogger class provides a simplified interface for logging messages with a predefined structure, including the path of the log origin.
Example usage in service/storage layer functions:
from fastapi import Depends
from ..core.logger import AppLogger
from ..dependencies.logger import get_app_logger
# For service layer
def get_service_logger(logger: AppLogger = Depends(lambda: get_app_logger("service"))):
return logger
def my_service_function(logger: AppLogger = Depends(get_service_logger)):
logger.info({"key": "value"})
# For storage layer
def get_users(conn: Connection, trace_id: str, logger: AppLogger) -> List[Dict[str, Any]]:
logger.info({"trace_id": trace_id})
with conn.cursor() as cur:
cur.execute("SELECT id, username, email FROM users;")
return cur.fetchall()
# Storage layer functions now use the logger passed from the service layer instead of re-initializing it.This will produce a log entry similar to:
{"timestamp": "...", "level": "INFO", "message": "{"path": "service.my_function", "key": "value"}"}POST /register: Register a new user.- Request Body:
{ "username": "yourusername", "email": "user@example.com", "password": "yourpassword" } - Error Responses: Include a
trace_idfor debugging.
- Request Body:
POST /login: Log in to get an access token.- Request Body (form-data):
username: Your emailpassword: Your password
- Error Responses: Include a
trace_idfor debugging.
- Request Body (form-data):
POST /refresh: Refresh an access token.- Request Body:
{ "refresh_token": "your_refresh_token" } - Error Responses: Include a
trace_idfor debugging.
- Request Body:
A TraceIdMiddleware is implemented to inject a unique trace_id into every request. This trace_id is:
- Included in the
X-Trace-IDresponse header. - Propagated through the application layers (handler, service, storage) for consistent logging.
- Included in error responses to aid in debugging.
Example log entry with trace_id:
{"timestamp": "...", "level": "INFO", "message": "{"trace_id": "...", "request": {...}, "response": {...}, "process_time_seconds": ...}"}Example error response with trace_id:
{
"detail": {
"message": "Email already registered",
"trace_id": "..."
}
}GET /users/: Get a list of users.GET /users/me: Get the current logged-in user.
To run the tests, you'll need a separate test database. Set the TEST_DATABASE_URL environment variable:
export TEST_DATABASE_URL="postgresql://postgres:localdb123@localhost/test_db_1"Then, run the tests:
uv run pytestIntegration tests (tests/test_routers.py) verify the end-to-end flow through the API endpoints, including middleware, service, and storage interactions. These tests ensure that the trace_id is correctly handled in requests, responses, and error details.
- Refactor Trace and Logger
- Add more basic functionality:
- CRUD users
- Authorization
- Dockerize