Setting Up

FastAPI Rebuild › Chapter 1

Project Setup

Scaffold the app, configure the virtual environment, and get FastAPI running locally

Chapter 1 of 10 Python & FastAPI Project: osztromok.com rebuild
Chapter Milestone

By the end of this chapter you will have a running FastAPI application with the correct project folder structure, a configured virtual environment, all dependencies installed, and a dev server you can reload instantly as you work. This is the foundation every other chapter builds on.

1 Why we are switching to FastAPI

Your current osztromok.com is a PHP CMS backed by MySQL. It works well for the Subject → Subtopic → Page hierarchy, but you hit a hard wall as soon as content needed to go deeper — the kanji grid linking to individual character pages, for example. The root cause was that Apache's rewrite rules treat real filesystem directories as a special case, so a /japan/ folder on disk breaks the router for the /japan/ subject slug.

FastAPI solves this because routing is defined entirely in Python code. There is no .htaccess, no filesystem ambiguity. A route /japan/kanji/{character} is just a function — it runs when the URL matches, regardless of what directories exist on disk. Static files (like the kanji HTML pages in /resources/) are mounted explicitly at a path you choose, completely separately from the dynamic routes.

If you know Java/Spring Boot: FastAPI is Python's equivalent of Spring Boot with @GetMapping route annotations, an embedded server (uvicorn instead of Tomcat), and a dependency-injection system. The mental model maps across almost directly.

The FastAPI stack vs the PHP stack

Current (PHP)New (FastAPI)
Apache + mod_rewriteuvicorn / gunicorn
PHP router (index.php)FastAPI app (main.py)
.htaccess rewrite rulesPython @app.get() decorators
PHP HTML templatesJinja2 templates
PDO / raw SQLSQLAlchemy ORM
$_SESSIONitsdangerous signed sessions
Apache serves /resources/StaticFiles mount at /resources/

2 Prerequisites

Before starting, confirm the following are available on the machine where you are developing (your Windows PC, the Pi, or a Linux VM — all work):

Terminal bash
# Check Python version — need 3.11 or newer
$ python3 --version
Python 3.11.9

# Check pip is available
$ pip3 --version
pip 24.0
Windows users: use python instead of python3, and run commands in PowerShell or Windows Terminal. Everything else in this course is identical.

3 Project folder structure

Create the following folder structure. You don't need to create every file yet — just the folders and the empty files shown. We will fill them in over the next chapters.

osztromok/ <-- project root ├── app/ <-- all Python application code lives here │ ├── __init__.py │ ├── main.py <-- FastAPI app instance, startup │ ├── database.py <-- SQLAlchemy engine & session factory │ ├── models.py <-- ORM models (subjects, pages, etc.) │ ├── routers/ <-- one file per group of routes │ │ ├── __init__.py │ │ ├── cms.py <-- /{subject}/{subtopic}/{page} │ │ ├── special.py <-- kanji, deep routes, catch-alls │ │ └── admin.py <-- admin CRUD (Chapter 8) │ ├── templates/ <-- Jinja2 HTML templates (Chapter 3) │ │ ├── base.html │ │ ├── home.html │ │ ├── page.html │ │ └── admin/ │ │ ├── login.html │ │ └── dashboard.html │ └── static/ <-- CSS, JS, images served at /static/ │ ├── css/ │ ├── js/ │ └── images/ ├── resources/ <-- static pages served at /resources/ (kanji, etc.) │ └── japanese/ │ └── kanji/ <-- kanji_水.html, kanji_食.html, etc. ├── .env <-- secrets (DB password, secret key) — never commit this ├── requirements.txt └── README.md

The key design decision is that application code lives inside app/ and static resources live outside it in resources/. This mirrors exactly what is already on your server — and means there is never a conflict between a filesystem folder and a dynamic CMS route.

Create the folder structure bash
$ mkdir -p osztromok/app/routers
$ mkdir -p osztromok/app/templates/admin
$ mkdir -p osztromok/app/static/{css,js,images}
$ mkdir -p osztromok/resources/japanese/kanji
$ cd osztromok
$ touch app/__init__.py app/routers/__init__.py
$ touch app/main.py app/database.py app/models.py
$ touch app/routers/cms.py app/routers/special.py app/routers/admin.py

4 Virtual environment

A virtual environment isolates this project's dependencies from everything else on your system. This is the Python equivalent of a Maven pom.xml or an npm package.json — except it also handles the runtime itself.

From inside the osztromok/ folder bash
# Create the virtual environment (once, per project)
$ python3 -m venv .venv

# Activate it — Linux / Mac / Git Bash
$ source .venv/bin/activate

# Activate it — Windows PowerShell
PS> .venv\Scripts\Activate.ps1

# Your prompt should now show (.venv)
(.venv) $ 
You need to activate the virtual environment every time you open a new terminal for this project. If you use VS Code, open the project folder and select the .venv interpreter — it will activate automatically in the integrated terminal.

5 Dependencies

Create requirements.txt in the project root with these packages. Every package here will be used somewhere across the ten chapters.

requirements.txt text
# Web framework and ASGI server
fastapi==0.115.5
uvicorn[standard]==0.32.0

# Templating
jinja2==3.1.4

# Database
sqlalchemy==2.0.36
pymysql==1.1.1

# Forms and file uploads
python-multipart==0.0.12

# Environment variables from .env file
python-dotenv==1.0.1

# Session signing (Chapter 7)
itsdangerous==2.2.0
Install everything bash
(.venv) $ pip install -r requirements.txt

What each package does

PackageRole in the project
fastapiThe web framework — routing, request/response, dependency injection
uvicornASGI server that runs the app (dev) — gunicorn replaces it in production
jinja2HTML template engine — replaces PHP's inline HTML embedding
sqlalchemyORM — maps your existing DB tables to Python classes
pymysqlMySQL driver used by SQLAlchemy to talk to the database
python-multipartRequired for HTML form handling (admin panel)
python-dotenvLoads DB credentials from a .env file so they're not in code
itsdangerousSigns and verifies session cookies for admin authentication

6 Environment file

Create a .env file in the project root for secrets. This file is never committed to version control — add .env to your .gitignore immediately.

.env env
DB_HOST=localhost
DB_PORT=3306
DB_NAME=osztromok_db
DB_USER=your_db_username
DB_PASSWORD=your_db_password

# Generate a strong random key: python3 -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=replace_with_a_long_random_string
Security: the .env file contains credentials that give full access to your database. Never paste it into a chat, commit it to git, or put it in a public folder. On the production server it lives outside the web root entirely.

7 The application entry point — main.py

This is the file that creates the FastAPI application instance, mounts static directories, and includes the route modules. Think of it as the equivalent of your PHP index.php front controller — but instead of being the file that handles requests, it is the file that configures how requests will be handled.

app/main.py python
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse

# ── Create the FastAPI application instance ─────────────────────────────
app = FastAPI(
    title="osztromok.com",
    description="Personal learning website — FastAPI rebuild",
    version="2.0.0",
)

# ── Mount static file directories ───────────────────────────────────────
# /static/  → app/static/    (CSS, JS, images used by templates)
# /resources/ → resources/   (standalone HTML pages: kanji, resumes, etc.)
#
# These are served DIRECTLY by FastAPI — they bypass all route handlers.
# This is how we avoid the Apache directory-conflict problem permanently.
app.mount(
    "/static",
    StaticFiles(directory="app/static"),
    name="static",
)
app.mount(
    "/resources",
    StaticFiles(directory="resources"),
    name="resources",
)

# ── Temporary smoke-test route ───────────────────────────────────────────
# We will replace this with a proper Jinja2 template in Chapter 3.
@app.get("/", response_class=HTMLResponse)
async def home():
    return """
    <html>
    <head><title>osztromok.com — FastAPI</title></head>
    <body style="font-family:sans-serif;background:#0d1117;color:#e6edf3;
                 display:flex;align-items:center;justify-content:center;
                 min-height:100vh;margin:0">
      <div style="text-align:center">
        <h1 style="color:#00b4d8">osztromok.com</h1>
        <p style="color:#8b949e">FastAPI is running ✓</p>
        <p style="color:#6e7681;font-size:.85rem">Chapter 1 complete — templates coming in Chapter 3</p>
      </div>
    </body>
    </html>
    """


# ── Routers will be included here in later chapters ─────────────────────
# from app.routers import cms, special, admin
# app.include_router(cms.router)
# app.include_router(special.router)
# app.include_router(admin.router)
StaticFiles mounts and the kanji problem: by mounting /resources explicitly here, FastAPI serves those files directly — there is no conflict with a CMS route called /japan/kanji/kanji-tiles because the two paths are completely different. The old Apache conflict simply cannot happen with this architecture.

8 Running the development server

uvicorn is the ASGI server that runs your FastAPI app. The --reload flag makes it watch for file changes and restart automatically — essential during development.

From the project root (osztromok/) bash
(.venv) $ uvicorn app.main:app --reload --port 8000

INFO:     Will watch for changes in these directories: ['/path/to/osztromok']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process using WatchFiles
INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Open http://127.0.0.1:8000 in your browser. You should see the teal "osztromok.com — FastAPI is running ✓" page.

What app.main:app means

app.main:app tells uvicorn: look in the app package (folder), inside the main module (main.py), and find the object named app (our FastAPI instance). This is the same pattern as telling Java to run com.example.Application.main().

Interactive API docs — free with FastAPI

FastAPI auto-generates interactive API documentation. While you are running the dev server, visit these URLs:

Auto-generated docs
http://127.0.0.1:8000/docs      # Swagger UI — interactive API explorer
http://127.0.0.1:8000/redoc     # ReDoc — alternative documentation view

These are most useful when you add JSON API routes later. For now they just show your single / route — but in Chapter 8 (Admin CRUD) they will be invaluable for testing endpoints before the HTML forms are built.

9 How FastAPI routing works — the key concept

Before we move on, it is worth understanding what the @app.get("/") decorator actually does, because routing is the central concept for the next three chapters.

Route anatomy python
# This is a route handler.
# @app.get("/")  → register this function for GET requests to "/"
# async def      → non-blocking (can handle many requests simultaneously)
# home()         → the function name — used internally but not in the URL

@app.get("/")
async def home():
    return {"message": "hello"}

# Path parameters use curly braces — they become function arguments.
# FastAPI validates their type automatically.

@app.get("/{subject}/{subtopic}/{page}")
async def cms_page(subject: str, subtopic: str, page: str):
    return {"subject": subject, "subtopic": subtopic, "page": page}

# A request to /japan/kanji/kanji-tiles would call this with:
#   subject  = "japan"
#   subtopic = "kanji"
#   page     = "kanji-tiles"
# No .htaccess. No filesystem check. Just Python.

Routes are matched in the order they are registered. More specific routes must come before general ones. We will use this in Chapter 6 to place the kanji route before the generic three-segment CMS route.

✓ Chapter 1 Complete — Milestone reached

  • Project structure created — all folders and empty files in place
  • Virtual environment configured and activated with all dependencies installed
  • .env file in place for secrets — ready for DB credentials in Chapter 2
  • main.py written — FastAPI instance created, static directories mounted, first route working
  • Dev server runninghttp://127.0.0.1:8000 returns a page
  • StaticFiles mounts configured — /resources/ will serve kanji pages without conflict

Quick Reference — Chapter 1

uvicorn app.main:app --reload Start dev server with auto-reload
source .venv/bin/activate Activate virtual environment (Linux/Mac)
pip install -r requirements.txt Install all project dependencies
pip freeze > requirements.txt Save current installed packages to file
http://127.0.0.1:8000/docs Auto-generated Swagger API documentation
@app.get("/path/{param}") Define a GET route with a path parameter
StaticFiles(directory="...") Serve a folder of files at a URL prefix
python -c "import secrets; print(secrets.token_hex(32))" Generate a secure random SECRET_KEY