Skip to content

Repository files navigation

FastAPI Playground

CI Lint Python 3.14+ License: MIT

A FastAPI application demonstrating Firebase Authentication, Firestore CRUD operations, and modern Python development workflow using uv (dependency & virtualenv manager) and just (task runner).

Python logo

Python logo from python.org. Trademark of the Python Software Foundation.

Features

  • Layered middleware architecture with security headers, CORS, request IDs, and structured access logs via fastapi-request-observability
  • Request-scoped logging with incoming W3C Trace Context correlation metadata
  • RFC 9457 Problem Details for FastAPI error responses, including field-level validation errors
  • JSON and CBOR request/response content negotiation using Content-Type and Accept
  • Cursor-based pagination with RFC 8288 Link headers
  • OpenAPI 3.1 documentation with Swagger UI and ReDoc
  • Firebase Authentication with ID token verification and revocation checks
  • Firestore persistence with async transactional operations
  • Health check endpoint (/health) for liveness probes

API Design Principles

URI Design

  • Use plural nouns for collections (/items, not /item)
  • Avoid verbs in URIs; let HTTP methods convey the action
  • Return resources directly without wrapper envelopes
  • Use snake_case consistently for public JSON and CBOR properties, request parameters, and persisted Firestore fields

HTTP Methods & Status Codes

Method Purpose Success Status
GET Retrieve resource(s) 200 OK
POST Process input and return a computed representation 200 OK
POST Create a resource 201 Created; persistent resources include a Location header
PATCH Partial update 200 OK
DELETE Remove a resource 204 No Content

Error Responses

Errors follow RFC 9457 Problem Details and honor content negotiation:

{
  "title": "Not Found",
  "status": 404,
  "detail": "Profile not found"
}

Validation errors (422) include detailed field locations:

{
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "validation failed",
  "errors": [
    {"location": "body.email", "message": "value is not a valid email address", "value": "invalid"}
  ]
}

Modeled responses advertise their standalone JSON Schema through an RFC 8288 Link: </schemas/Model.json>; rel="describedBy" header. $schema belongs to the schema document itself, not to each API response instance.

Content Negotiation

  • API responses with a body default to application/json. CBOR is optional and selected only by an explicit Accept: application/cbor; wildcards and equal quality values keep JSON.
  • An explicit Accept value that excludes every supported success representation returns 406 before a representation-bearing endpoint executes. A 204 response has no representation, so Accept does not gate it.
  • Schema discovery returns only application/schema+json. Strict clients must accept that media type or a matching wildcard; application/json does not match a distinct +json subtype.
  • Errors keep their original status. They use RFC 9457 application/problem+json by default, or registered application/cbor when explicitly preferred. Unsupported error preferences fall back to JSON; the unregistered application/problem+cbor media type is not implemented.
  • JSON and CBOR request bodies are selected independently with Content-Type.
  • /openapi.json, /api-docs, and /api-redoc are fixed FastAPI documentation assets outside optional CBOR negotiation.

Pagination

  • Cursor-based tokens for stability
  • Links provided via HTTP Link header per RFC 8288

Requirements

  • Python 3.14+
  • uv package manager (the minimum supported version is enforced in both project manifests)
  • just command runner
  • Firebase project with Authentication and Firestore enabled
  • Firebase CLI for emulators and Functions deployment

Quick Start

git clone <repository-url>
cd fastapi-playground
cp .env.example .env
# Set FIREBASE_PROJECT_ID in .env
just install        # Install dependencies via uv
just serve          # Start dev server at http://127.0.0.1:8080

Then visit:

  • http://localhost:8080/health - service health probe
  • http://localhost:8080/api-docs - interactive API explorer (Swagger UI)
  • http://localhost:8080/api-redoc - API documentation (ReDoc)

Sample request:

curl -s localhost:8080/health | jq

Environment Variables

Copy .env.example to .env and customize:

cp .env.example .env
Variable Description Default
PORT Server listen port 8080
ENVIRONMENT Runtime mode; production enables HSTS and strips 5xx response extensions production
LOG_LEVEL Application log verbosity (DEBUG, INFO, WARNING, ERROR, or CRITICAL) INFO
FIREBASE_PROJECT_ID Firebase/GCP project ID -
FIRESTORE_DATABASE Firestore database ID (default)
GOOGLE_APPLICATION_CREDENTIALS Service account JSON path (local dev) -
CORS_ORIGINS JSON array or comma-separated allowed origins -
MAX_REQUEST_SIZE_BYTES Request body size limit 1000000

Firebase and Cloud Logging MCP clients

The workspace MCP configurations in .vscode/mcp.json and .codex/config.toml use the Firebase CLI server and the managed Cloud Logging server. Firebase uses the credentials available to the Firebase CLI. Cloud Logging reads the quota project and a short-lived Application Default Credentials access token from the shell environment.

First align the gcloud default project and the Application Default Credentials quota project:

gcloud config set project PROJECT_ID
gcloud auth application-default login
gcloud auth application-default set-quota-project PROJECT_ID

For zsh, add these public, secret-free definitions to ~/.zshrc:

export GOOGLE_CLOUD_PROJECT="PROJECT_ID"
export GOOGLE_CLOUD_ACCESS_TOKEN="$(gcloud auth application-default print-access-token)"

Run source ~/.zshrc, then restart VS Code or Codex so the client inherits the variables. The access token is short-lived; opening a new shell regenerates it, while a long-running client must be restarted after a refresh. Never commit the expanded token. Keep this convenience setup to a trusted workstation because child processes inherit the token. See GCP.md for IAM and troubleshooting details.

Project Layout

.agents/skills/        Six portable project workflows with Codex UI metadata
.github/agents/       Evidence-based security review profile for GitHub Copilot
app/
  main.py              # FastAPI composition, lifespan, and outer ASGI middleware
  dependencies.py      # Dependency injection (CurrentUser, ProfileServiceDependency)
  api/                 # API route handlers
    health.py          # Health check endpoint
    hello.py           # Hello greeting endpoints
    items.py           # Items with pagination
    profile.py         # Profile CRUD (Firebase Auth protected)
  auth/                # Firebase authentication
    firebase.py        # Token verification, FirebaseUser
  core/                # Configuration and infrastructure
    config.py          # Settings class (pydantic-settings)
    logging.py         # Structured JSON logging configuration
    firebase.py        # Firebase Admin SDK and async Firestore client
    exception_handler.py  # RFC 9457 Problem Details
    content_negotiation.py  # RFC 9110 media-type selection
    cbor.py            # CBOR request and response adaptation
    openapi.py         # Reusable response contract metadata
    schema_links.py    # RFC 8288 schema links
    validation.py      # Validation error formatting and redaction
  exceptions/          # Domain exceptions using fastapi-problem
    profile.py         # ProfileNotFoundError, ProfileAlreadyExistsError
  middleware/          # ASGI middleware stack
    body_limit.py      # Request size guard (413 on oversized)
    security.py        # Security headers (HSTS, X-Frame-Options)
  models/              # Pydantic schemas
    error.py           # ProblemResponse schema
    types.py           # Shared types (NormalizedEmail, Phone, UTCDateTime)
    health/            # Health response models
    hello/             # Hello response models
    items/             # Items response models
    profile/           # Profile domain models
  pagination/          # Cursor-based pagination
    cursor.py          # Cursor encoding/decoding
    link.py            # RFC 8288 Link header builder
    paginator.py       # Pagination helper
  services/            # Business logic layer
    profile/           # ProfileService with Firestore operations
tests/
  unit/                # Unit tests (mocked dependencies)
  integration/         # API route tests (TestClient)
  e2e/                 # Firebase emulator tests (local only)
  helpers/             # Shared test utilities
functions/             # Firebase Cloud Functions (Python 3.14)
  main.py              # HTTP dad-joke function
  pyproject.toml       # Functions-specific dependencies
  tests/               # Isolated function contract tests

Repository guidance follows the canonical AGENTS.md format. Portable skills use the canonical Agent Skills specification and documentation, with the detailed format specification, under .agents/skills/. See AGENTS.md for the working rules and current skill catalog.

Routes

All routes use paths without trailing slashes (redirect_slashes=False).

Method Path Auth Description
GET /health No Health check probe
GET /v1/hello No Default greeting
POST /v1/hello No Generate a personalized greeting (200 OK)
GET /v1/items No List items with pagination
POST /v1/profile Yes Create user profile
GET /v1/profile Yes Get user profile
PATCH /v1/profile Yes Update user profile
DELETE /v1/profile Yes Delete user profile
GET /schemas/{schema_name} No Retrieve a generated JSON Schema

Protected routes require Authorization: Bearer <Firebase ID token> header.

Development

Build and Test

just lint               # Check Python style and GitHub Actions security
just typing             # Type-check the FastAPI app
just typing-functions   # Type-check the separate Functions project
just test-functions     # Run isolated Functions tests
just test               # Run unit + integration tests
just test-unit          # Run unit tests only
just test-integration   # Run integration tests only
just test-e2e           # Run emulator E2E tests, or skip when emulators are absent
just test-all           # Run every test tier
just cov                # Generate HTML and JSON coverage reports

Justfile Commands

Command Description
just serve Start dev server with hot reload
just browser Open dev server in browser
just lint Check Ruff linting/formatting and GitHub Actions with zizmor
just typing Type checking via ty
just typing-functions Type-check the separate Functions project
just test-functions Test the separate Functions project
just test Unit + integration tests
just test-e2e Firebase emulator E2E tests
just test-all All test tiers
just cov Coverage report (html/json)
just check Full app and Functions lint, type, test, and dependency-manifest checks
just emulators Start Firebase emulators for E2E

Run just to see all available commands.

Dependencies

just install         # Install/sync dependencies
just install-functions  # Install/sync Functions dependencies
just update          # Upgrade root and Functions dependencies
just fresh           # Clean + reinstall

Adding Routes

  1. Create models in app/models/<resource>/ with request/response schemas
  2. Add domain exceptions in app/exceptions/ if needed
  3. Implement service logic in app/services/<resource>/
  4. Create handler in app/api/<resource>.py using APIRouter
  5. Register business routers in app/api/__init__.py; keep health and schema discovery unversioned in app/main.py
  6. Add unit tests for service, integration tests for routes

Container

just container-build    # Build image
just container-up       # Run container
just container-logs     # View container logs
just container-down     # Stop container

Or with Docker/Podman CLI:

docker build -t fastapi-playground:latest .
docker run --rm -p 8080:8080 --env-file .env fastapi-playground:latest

Profile requests also require Application Default Credentials or a service-account credential mounted into the container. The just container-up recipe mounts service_account.json by default; override its creds argument when using another local path.

Deployment

Google Cloud Run

# Build and push to Artifact Registry
GIT_SHA="$(git rev-parse HEAD)"
gcloud builds submit --tag "REGION-docker.pkg.dev/PROJECT_ID/REPO/fastapi-playground:${GIT_SHA}"

# Deploy the repository-built standalone image
gcloud run deploy fastapi-playground \
  --image "REGION-docker.pkg.dev/PROJECT_ID/REPO/fastapi-playground:${GIT_SHA}" \
  --platform managed \
  --region REGION

The repository Dockerfile produces a standalone image containing the operating system and Python runtime. Do not combine that image with --base-image or --automatic-updates: Cloud Run automatic base image updates require either a scratch-based application image or a different buildpack source-deployment flow. Adopting either would change the established build and image convention and requires separate verification. Rebuild the standalone image to pick up operating-system, Python, and dependency updates.

The image trusts Cloud Run's forwarded proxy headers so application URLs and HTTPS-only security headers reflect the original client request after Cloud Run terminates TLS. Request logs correlate an incoming valid traceparent; the middleware does not create tracing spans.

For detailed infrastructure setup, see GCP.md.

Firebase Cloud Functions

cd functions
uv venv --python 3.14 venv
uv pip install --python venv/bin/python -r requirements.txt
firebase deploy --only functions --project PROJECT_ID

The model-backed function is private. Grant intended callers roles/run.invoker on its backing Cloud Run service and send an ID token when invoking it. The Function deploys in europe-west4; its Vertex AI client uses the global endpoint and the auto-updating gemini-pro-latest alias. See functions/README.md for the alias trade-offs and Cloud Functions documentation.

CI/CD

GitHub Actions workflows in .github/workflows/:

Workflow Description
app-ci.yml App and Functions checks, container build, and app test coverage
app-lint.yml Fast Ruff linting and formatting feedback
zizmor.yml Upload GitHub Actions security findings to code scanning
labeler.yml Automatic PR labeling
labeler-manual.yml Manual labeling for historical PRs
dependabot-auto-merge.yml Auto-merge Dependabot minor/patch updates

Dependabot is configured in .github/dependabot.yml for automated dependency updates.

Contributing

See AGENTS.md for coding guidelines and conventions.

License

MIT - see LICENSE.