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.

FastAPI Blueprint 01: Intro and Architecture Overview

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?