FastAPI Blueprint 01: Intro and Architecture Overview
Kick off the series by understanding what we’re building, why microservices matter, and how each component fits into the bigger picture.

What you will learn
- What this series builds and why
- How the template is structured so you can reuse it for auth, sales, reports, or social feeds
- The roles of PostgreSQL, MongoDB, Redis, and Kafka in this architecture
- A ready starter project with FastAPI, typed settings, basic logging, versioned routing, and a health endpoint
Why microservices for this template
We want a template that can be reused for different domains without copy paste chaos. A microservice works well when you keep boundaries clear and communication explicit. You get independent deploys, tech freedom per service, and the ability to scale hot paths only.
Tradeoffs to accept:
- More moving parts
- You need good observability and testing
- Data consistency requires care
High level architecture
[Client or Another Service]
|
HTTP/REST
v
+-----------------+
| FastAPI App |
| Routers |
| DI glue |
+--------+--------+
| Service layer calls ports
+---------+-----------------------------+
| |
[Database Ports] [Messaging Port]
| | | |
Postgres MongoDB Redis Kafka Client
- Routers expose HTTP endpoints
- Service layer holds use cases and rules
- Ports define interfaces for infrastructure
- Adapters implement those ports for Postgres, MongoDB, Redis, Kafka
This is the clean architecture vibe. Business code depends on interfaces, not concrete tech. You can swap a datastore or add one without rewriting your core logic.
When to use each datastore
- PostgreSQL: transactional data, strong consistency, relations
- MongoDB: flexible documents, analytics-style reads, content feeds
- Redis: caching, sessions, rate limits, short lived data
- Kafka: events between services, decoupling write and read models
You can use more than one in the same service. Example: write orders in Postgres, cache summaries in Redis, publish order events to Kafka.
What we build today
- Project scaffold that will scale for the rest of the series
- App factory with typed settings
- Versioned API prefix
- Health and version endpoints
- Logging that plays nice in containers
- Placeholders for databases and Kafka that we will fill later
Code for Step 1
Folder tree
fastapi-blueprint/
├─ pyproject.toml # or requirements.txt if you prefer pip
├─ README.md
├─ .env.example
├─ .gitignore
├─ Dockerfile
├─ app/
│ ├─ __init__.py
│ ├─ core/
│ │ ├─ config.py # Pydantic settings
│ │ ├─ logging.py # base logging setup
│ │ └─ version.py # service version
│ ├─ main.py # app factory
│ ├─ api/
│ │ ├─ __init__.py
│ │ ├─ deps.py # shared dependencies
│ │ ├─ v1/
│ │ │ ├─ __init__.py
│ │ │ └─ routes_health.py # /health, /version
│ ├─ domain/ # business rules later
│ │ └─ __init__.py
│ ├─ ports/ # interfaces
│ │ ├─ __init__.py
│ │ ├─ db_port.py
│ │ └─ messaging_port.py
│ └─ adapters/ # implementations later
│ ├─ __init__.py
│ ├─ postgres_adapter.py
│ ├─ mongodb_adapter.py
│ ├─ redis_adapter.py
│ └─ kafka_adapter.py
└─ tests/
└─ test_health.py
pyproject.toml (Poetry)
If you prefer pip, I include a requirements.txt
right after.
[tool.poetry]
name = "fastapi-blueprint"
version = "0.1.0"
description = "FastAPI microservice template - Step 1 scaffold"
authors = ["You <you@example.com>"]
readme = "README.md"
packages = [{ include = "app" }]
[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.115.0"
uvicorn = { extras = ["standard"], version = "^0.30.0" }
pydantic-settings = "^2.4.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.0"
httpx = "^0.27.0"
pytest-asyncio = "^0.23.7"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requirements.txt (pip alternative)
fastapi==0.115.0
uvicorn[standard]==0.30.0
pydantic-settings==2.4.0
pytest==8.3.0
httpx==0.27.0
pytest-asyncio==0.23.7
.env.example
APP_NAME=fastapi-blueprint
APP_ENV=local
API_V1_STR=/api/v1
LOG_LEVEL=INFO
app/core/version.py
SERVICE_NAME = "fastapi-blueprint"
SERVICE_VERSION = "0.1.0"
app/core/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "fastapi-blueprint"
app_env: str = "local"
api_v1_str: str = "/api/v1"
log_level: str = "INFO"
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
settings = Settings()
app/core/logging.py
import logging
import sys
def setup_logging(level: str = "INFO") -> None:
handler = logging.StreamHandler(sys.stdout)
fmt = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
handler.setFormatter(logging.Formatter(fmt))
root = logging.getLogger()
root.handlers.clear()
root.addHandler(handler)
root.setLevel(level.upper())
app/api/v1/routes_health.py
from fastapi import APIRouter
from app.core.version import SERVICE_NAME, SERVICE_VERSION
router = APIRouter()
@router.get("/health", tags=["system"])
async def health() -> dict:
return {"status": "ok"}
@router.get("/version", tags=["system"])
async def version() -> dict:
return {"service": SERVICE_NAME, "version": SERVICE_VERSION}
app/api/deps.py
# Shared dependencies live here. Example:
# from fastapi import Depends
# def get_current_user(...) -> User: ...
app/ports/db_port.py
from typing import Protocol, Any
class DatabasePort(Protocol):
async def connect(self) -> None: ...
async def close(self) -> None: ...
# You can add generic methods or keep it minimal for now
app/ports/messaging_port.py
from typing import Protocol, Mapping, Any
class MessagingPort(Protocol):
async def publish(self, topic: str, key: bytes | None, value: bytes, headers: Mapping[str, bytes] | None = None) -> None: ...
async def start_consumer(self) -> None: ...
async def stop_consumer(self) -> None: ...
app/adapters placeholders
# app/adapters/postgres_adapter.py
# Implement DatabasePort for Postgres in a later lesson
# app/adapters/mongodb_adapter.py
# Implement DatabasePort for MongoDB in a later lesson
# app/adapters/redis_adapter.py
# Implement DatabasePort for Redis in a later lesson
# app/adapters/kafka_adapter.py
# Implement MessagingPort for Kafka in a later lesson
app/main.py
from fastapi import FastAPI
from app.core.config import settings
from app.core.logging import setup_logging
from app.api.v1.routes_health import router as health_router
def create_app() -> FastAPI:
setup_logging(settings.log_level)
app = FastAPI(
title=settings.app_name,
version="0.1.0",
docs_url="/docs",
redoc_url="/redoc",
)
# Versioned API
app.include_router(health_router, prefix=settings.api_v1_str)
@app.get("/", tags=["system"])
async def root():
return {"message": "Service running", "name": settings.app_name}
return app
app = create_app()
tests/test_health.py
import pytest
from httpx import AsyncClient
from app.main import create_app
@pytest.mark.asyncio
async def test_health():
app = create_app()
async with AsyncClient(app=app, base_url="http://test") as ac:
resp = await ac.get("/api/v1/health")
assert resp.status_code == 200
assert resp.json()["status"] == "ok"
Dockerfile
FROM python:3.12-slim
WORKDIR /app
# If you use Poetry
RUN pip install --no-cache-dir poetry==1.8.3
COPY pyproject.toml poetry.lock* /app/
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi
# If you use pip instead, comment out the Poetry block above and:
# COPY requirements.txt /app/
# RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
ENV PYTHONUNBUFFERED=1
ENV PORT=8080
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
Quick start
Using Poetry:
poetry install
cp .env.example .env
poetry run uvicorn app.main:app --reload
Using pip:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload
Test it:
curl http://127.0.0.1:8000/api/v1/health
curl http://127.0.0.1:8000/api/v1/version
You now have a clean skeleton that we can extend in the next lessons. Step 2 will set up the full project environment, refine config handling, and lock in the folder conventions so adding databases and Kafka later feels natural.
Want me to drop this into a Ghost-ready post file and a GitHub README, or generate a zip so you can download the scaffold in one shot?