GlobeTrotter is a monolithic Flask application that serves as the starting point for a semester-long capstone project.
Students build the monolith first, then refactor it into microservices, and finally deploy it to the cloud with resilience patterns using Docker, Kubernetes, and cloud-native tooling.
.
├── app/
│ ├── __init__.py # Flask app factory
│ ├── models.py # Data models and JSON file I/O
│ ├── auth.py # Registration, login, JWT handling
│ ├── destinations.py # Destination search endpoint
│ ├── recommendations.py # Personalised recommendations endpoint
│ ├── itineraries.py # Create / list itineraries
│ └── main.py # App entry point
├── data/
│ ├── destinations.json # Static destination catalogue (seed data)
│ ├── users.json # Created at runtime
│ └── itineraries.json # Created at runtime
├── tests/ # Placeholder for future tests
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md
| Method | Endpoint | Auth required | Description |
|---|---|---|---|
| POST | /register |
No | Register a new user |
| POST | /login |
No | Authenticate and receive a JWT token |
| GET | /destinations |
No | Search the destination catalogue |
| GET | /recommendations |
Yes (JWT) | Get personalised recommendations |
| POST | /itineraries |
Yes (JWT) | Create a new itinerary |
| GET | /itineraries |
Yes (JWT) | List all itineraries for the logged-in user |
Protected routes expect the header:
Authorization: Bearer <your-token>
# Register
curl -X POST http://localhost:5000/register \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "s3cr3t", "preferences": ["beach", "food"]}'
# Login
curl -X POST http://localhost:5000/login \
-H "Content-Type: application/json" \
-d '{"username": "alice", "password": "s3cr3t"}'
# Save the returned token: TOKEN=<value from .token field>
# Search destinations
curl "http://localhost:5000/destinations?tag=beach&max_cost=100"
# Personalised recommendations
curl http://localhost:5000/recommendations \
-H "Authorization: Bearer $TOKEN"
# Create an itinerary
curl -X POST http://localhost:5000/itineraries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title": "Beach Escape", "destinations": ["Bali"], "start_date": "2025-07-01", "end_date": "2025-07-14"}'
# List itineraries
curl http://localhost:5000/itineraries \
-H "Authorization: Bearer $TOKEN"- Python 3.13+
- pip
# 1. Install dependencies
pip install -r requirements.txt
# 2. Start the server
python app/main.pyThe API will be available at http://localhost:5000.
# Install pytest if not already installed
pip install pytest
# Run all tests
pytest -qCurrently, the test suite covers:
- JWT token creation and decoding (
tests/test_auth.py) - JSON file I/O helpers (
tests/test_models_io.py) - Recommendation scoring and sorting (
tests/test_recommendations.py)
Place test files in the tests/ directory following the naming convention test_*.py. The CI pipeline runs pytest on every push.
The project uses ruff, black, and isort for code quality:
# Install tools
pip install ruff black isort
# Auto-format code
black .
isort .
# Check for linting issues
ruff check .Configuration is in pyproject.toml and .flake8.
Set up automatic linting before commit:
pip install pre-commit
pre-commit installA GitHub Actions workflow (.github/workflows/ci.yml) runs automatically on push and pull requests:
- Tests: Runs pytest across Python 3.11, 3.12, and 3.13
- Linting: Checks with ruff
- Import sorting: Validates with isort
- Formatting: Checks conformance with black
Go to the Actions tab on GitHub.
The project includes vulnerability scanning via pip-audit:
pip install pip-audit
pip-audit --format=jsonKnown vulnerabilities (as of Jul 28, 2026):
- PyJWT (2.8.0 → 2.13.0): Critical header parameter validation flaws
- Flask (2.3.3 → 3.1.3): Cache-header bypass in session handling
- Werkzeug (2.3.7 → 3.1.6): Multipart parser DoS and safe_join bypasses
- python-dotenv (1.0.0 → 1.2.2): Symlink following in setkey()
- pip (25.2 → 26.1.2): Console script path traversal and tar extraction issues
Recommendation: Upgrade dependencies in requirements.txt:
pip install --upgrade PyJWT Flask Werkzeug python-dotenv
pip freeze > requirements.txtCreate a .env file (copy from .env.example) to override defaults:
# Environment variables
SECRET_KEY=your-random-secret-key-here
FLASK_DEBUG=0
FLASK_ENV=production
PORT=5000Important: Never commit
.envto version control. Use.env.exampleas a template.
# Build and start the container
docker-compose up --build
# Stop containers
docker-compose down
# View logs
docker-compose logs -f globetrotterThe application will be available at http://localhost:5000 and includes a health check that verifies container readiness.
The Dockerfile now features:
- Multi-stage build: Separates dependencies layer from application layer (smaller production images)
- Non-root user: Runs as
appuser(enhanced security) - Health check: Built-in container health monitoring
- Python 3.13: Latest stable release
- Optimized caching: Virtual environment re-used across stages
The docker-compose.yml includes:
- Automatic restart on failure (up to 3 retries)
- Health check with monitoring endpoints
- Isolated network (
globetrotter-net) - Logging configuration with size limits (10 MB, 3 files)
- Volume mounts for development (live code changes)
To build a production-ready image:
docker build -t globetrotter:latest --target production .| Environment Variable | Default | Description |
|---|---|---|
SECRET_KEY |
globetrotter-secret-change-in-prod |
JWT signing key – must be overridden in production |
FLASK_DEBUG |
0 |
Set to 1 to enable Flask debug mode (development only) |
PORT |
5000 |
Port the app listens on |
PYTHONUNBUFFERED |
1 |
Unbuffered Python output (for container logging) |
Important: Always set
SECRET_KEYto a long, random value in production (e.g.python -c "import secrets; print(secrets.token_hex(32))").
All data is persisted in plain JSON files inside the data/ directory:
| File | Purpose |
|---|---|
data/destinations.json |
Static catalogue of travel destinations (seed data) |
data/users.json |
Registered users (created at runtime) |
data/itineraries.json |
User itineraries (created at runtime) |
Note:
data/*.json(exceptdestinations.json) are excluded from version control via.gitignore.