Setting Up
Project Setup
Scaffold the app, configure the virtual environment, and get FastAPI running locally
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.
@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_rewrite | uvicorn / gunicorn |
| PHP router (index.php) | FastAPI app (main.py) |
| .htaccess rewrite rules | Python @app.get() decorators |
| PHP HTML templates | Jinja2 templates |
| PDO / raw SQL | SQLAlchemy ORM |
| $_SESSION | itsdangerous 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):
# Check Python version — need 3.11 or newer $ python3 --version Python 3.11.9 # Check pip is available $ pip3 --version pip 24.0
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.
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.
$ 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.
# 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) $
.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.
# 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
(.venv) $ pip install -r requirements.txt
What each package does
| Package | Role in the project |
|---|---|
| fastapi | The web framework — routing, request/response, dependency injection |
| uvicorn | ASGI server that runs the app (dev) — gunicorn replaces it in production |
| jinja2 | HTML template engine — replaces PHP's inline HTML embedding |
| sqlalchemy | ORM — maps your existing DB tables to Python classes |
| pymysql | MySQL driver used by SQLAlchemy to talk to the database |
| python-multipart | Required for HTML form handling (admin panel) |
| python-dotenv | Loads DB credentials from a .env file so they're not in code |
| itsdangerous | Signs 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.
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
.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.
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)
/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.
(.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:
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.
# 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 running —
http://127.0.0.1:8000returns a page - StaticFiles mounts configured —
/resources/will serve kanji pages without conflict