FastAPI has quickly become one of the most popular Python web frameworks, and for good reason: it is fast, modern, and ships with compelling built-in features including automatic API documentation, data validation, and robust async support.
What We’re Building
The application developed throughout this series is a full-featured blog platform that exposes both a JSON REST API for programmatic access and HTML pages for users to browse in a web browser. Navigating to an API route returns JSON data, while the front-end pages consume that same API. FastAPI also provides two automatically generated documentation interfaces at /docs (Swagger UI) and /redoc, both reflecting every API endpoint without any manual configuration.
The application’s feature set includes user registration and login with secure password hashing and JWT tokens, a password-reset flow that uses background tasks to send emails, profile management with file uploads, and full CRUD operations for posts. The homepage includes dynamic pagination that fetches additional posts from the API on demand. Authorization is enforced throughout: a user can only edit or delete their own posts.
Over the course of the series, the data layer starts with SQLite via SQLAlchemy and then migrates to PostgreSQL. Pydantic models handle data validation, code is organized using FastAPI’s router system, and a dedicated section covers async/await — explaining when and how to use asynchronous versus synchronous code effectively. The series concludes with testing and deployment.
Project Setup
Create a new project directory. This series uses uv, a modern Python package manager, but everything works equally well with pip — simply create the directory manually and substitute pip install wherever uv add appears.
uv init fastapi_blog
cd fastapi_blogInstall FastAPI with its standard extras, which bundle the framework itself, the Uvicorn ASGI server, and the FastAPI CLI:
# with uv
uv add "fastapi[standard]"
# with pip
pip install "fastapi[standard]"The quotes around the package name are recommended because some shells mishandle bare square brackets.
Creating the Application
Inside the project directory, create main.py. The entire skeleton of a FastAPI application is two lines:
from fastapi import FastAPI
app = FastAPI()The app object is used to define all routes via decorators — a pattern that will feel familiar to anyone who has used Flask.
Defining Routes
Add a home route that responds to GET requests at the root URL:
@app.get("/")
def home():
return {"message": "Hello, World"}FastAPI automatically serializes the returned dictionary to JSON. Note the use of a plain def rather than async def — FastAPI handles synchronous functions perfectly well, and async usage will be addressed in its own dedicated section later in the series.
Running the Application
Use the FastAPI CLI to start the development server:
# with uv
uv run fastapi dev main.py
# with pip / direct install
fastapi dev main.pyThe dev command enables auto-reload (the server restarts automatically on every code change) and provides richer debugging output. In production, use fastapi run instead, which is optimized for performance.
With the server running, navigating to http://localhost:8000 returns the JSON response {"message": "Hello, World"}.
Automatic API Documentation
Without any additional configuration, FastAPI generates two interactive documentation interfaces:
/docs— Swagger UI, which lists every route and allows executing requests directly in the browser. The UI shows the curl command, request URL, and response body for each executed call, making it useful for manual API testing./redoc— a more modern-looking alternative presenting the same information.
Both are derived entirely from the code; no documentation needs to be written manually.
Working with Data
For now, posts are represented as a list of dictionaries, each containing an id, author, title, content, and date_posted. This is temporary — a proper database with SQLAlchemy replaces it later in the series.
posts: list[dict] = [
{
"id": 1,
"author": "...",
"title": "...",
"content": "...",
"date_posted": "..."
},
# ...
]An API endpoint that returns this data is defined exactly like the home route:
@app.get("/api/posts")
def get_posts():
return postsFastAPI automatically converts the list of dictionaries to a JSON array. Visiting /api/posts in the browser — or testing it through /docs — returns that array.
Returning HTML Responses
For routes intended to render HTML rather than JSON, import HTMLResponse and declare it as the response class on the decorator:
from fastapi.responses import HTMLResponse
@app.get("/", response_class=HTMLResponse)
def home():
return f"<h1>{posts[0]['title']}</h1>"To serve the same page at multiple URLs, stack decorators on a single function:
@app.get("/", response_class=HTMLResponse)
@app.get("/posts", response_class=HTMLResponse)
def home():
return f"<h1>{posts[0]['title']}</h1>"Both / and /posts now execute the same handler. Note the distinction between /posts (the human-facing HTML page) and /api/posts (the JSON API endpoint).
Keeping the Docs Clean
HTML routes intended for browser navigation should not clutter the API documentation. FastAPI provides the include_in_schema parameter for exactly this purpose:
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
@app.get("/posts", response_class=HTMLResponse, include_in_schema=False)
def home():
return f"<h1>{posts[0]['title']}</h1>"Setting include_in_schema=False keeps the routes fully functional while hiding them from /docs and /redoc, resulting in a clean separation between the JSON API surface and the human-facing pages.
Summary
At this point the application has FastAPI installed, multiple routes returning dummy data in both JSON and HTML form, and automatic API documentation that exposes only the API routes. The next topic is Jinja2 templating — replacing raw HTML strings with a proper template system that supports template inheritance, styling, and maintainable page structure.