Hands-on Project
A simple application (React frontend + FastAPI backend) used throughout the DevOps course.
In the workplace, the frontend and backend are typically in separate repositories (repos), each with its own CI/CD pipeline. Here, we put them in the same repo to simplify learning.
Architecture
Section titled “Architecture”┌──────────────┐ HTTP ┌───────────────────┐ SQL ┌──────────────┐│ │ /api/... │ │ │ ││ Frontend │──────────────▶│ Backend │──────────────▶│ PostgreSQL ││ (React + │ │ (FastAPI + │ │ (data) ││ nginx) │◀──────────────│ Python) │◀──────────────│ ││ │ JSON │ │ rows │ ││ port 80 │ │ port 8000 │ │ port 5432 │└──────────────┘ └───────────────────┘ └──────────────┘ │ │ │ └──────────────────────────────┴───────────────────────────────────┘ Docker Compose (a shared network)- The frontend is a web page (React) served by nginx. The user sees the task list.
- nginx acts as a reverse proxy:
/apirequests are forwarded to the backend. - The backend is a Python API (FastAPI). It manages tasks (create, list, toggle, delete).
- PostgreSQL stores the data. Without Docker (no
DATABASE_URL), the backend uses an in-memory list.
Structure
Section titled “Structure”.github/workflows/ ci.yml → CI/CD Pipeline (lint → test/integration → build → push)frontend/ → Vite + React (managed by Bun) Dockerfile → Multi-stage build (Bun → nginx) nginx.conf → Reverse proxy to the backendbackend/ → Python FastAPI (managed by uv) Dockerfile → Python image with uv main.py → The API (routes + storage) aws_client.py → The code that talks to AWS (S3, SQS) test_main.py → Unit tests test_integration.py → Integration tests (require Floci)floci/ → Local AWS (emulator, see below) docker-compose.yml → Starts Floci init/ready.d/ → Scripts run when Floci startsdocker-compose.yml → Backend + Frontend + PostgreSQLdocker-compose.floci.yml → Override: connects the backend to local AWSRun locally (without Docker)
Section titled “Run locally (without Docker)”Backend:
cd backenduv syncuv run uvicorn main:app --reload# The API runs on http://localhost:8000# Without DATABASE_URL → in-memory storage (no PostgreSQL needed)Frontend:
cd frontendbun installbun run dev# The frontend runs on http://localhost:3000# /api calls are proxied to the backendRun with Docker Compose
Section titled “Run with Docker Compose”docker compose up -d --build# Frontend: http://localhost (port 80)# Backend: http://localhost:8000# PostgreSQL: port 5432 (only accessible from the backend)Run local AWS (Floci)
Section titled “Run local AWS (Floci)”Floci is an AWS emulator: it imitates AWS on your machine. No account, no credit card, no bill. It’s what lets you practise S3, SQS, DynamoDB, Lambda, RDS, EC2 and Terraform without paying anything.
cd flocidocker compose up -d
# Check it's ready (should print 200)curl -s -o /dev/null -w "%{http_code}\n" http://localhost:4566/health
# Every AWS command targets the emulator with --endpoint-urlaws --endpoint-url http://localhost:4566 s3 lsTo connect the application to it:
docker compose -f docker-compose.yml -f docker-compose.floci.yml up -d --buildFull guide: AWS Locally.
API Endpoints
Section titled “API Endpoints”| Method | URL | Description |
|---|---|---|
GET | /api/tasks | List tasks |
POST | /api/tasks | Create a task ({"title": "..."}) |
PATCH | /api/tasks/{id} | Toggle done/not done |
DELETE | /api/tasks/{id} | Delete a task |
GET | /api/health | Health check |
Linting
Section titled “Linting”# Backend (Ruff)cd backend && uv run ruff check .
# Frontend (Oxlint)cd frontend && bunx oxlint .There are two families of tests, and they aren’t launched the same way.
Unit tests — fast, no dependencies:
cd backend && uv run pytest# 7 tests: GET, POST, PATCH, PATCH 404, DELETE, DELETE 404, healthIntegration tests — they really talk to S3 and SQS, so Floci is required:
cd floci && docker compose up -d && cd ../backend
AWS_ENDPOINT_URL=http://localhost:4566 uv run pytest -m integration# 4 tests: S3 upload/read, missing file, SQS message, endpoint guardBy default, uv run pytest skips the integration tests (configured in
pyproject.toml). That’s deliberate: fast tests must be runnable with nothing
installed.
The same unit tests against a real PostgreSQL — the code doesn’t change, only the environment variable appears:
DATABASE_URL=postgresql://user:pass@localhost:5432/tasks uv run pytest