Home / Blog / Building Microservices with FastAPI: A Practical Architecture Guide
Web Development

Building Microservices with FastAPI: A Practical Architecture Guide

AJAjish Stephen August 7, 2026 8 min read
Building Microservices with FastAPI: A Practical Architecture Guide

FastAPI's native async support and automatic request validation make it a strong fit for microservices, where a service typically spends much of its time waiting on other services rather than doing CPU-heavy work. This guide walks through a minimal two-service setup — an orders service and a inventory service — communicating over HTTP, containerized with Docker Compose.

Project structure

Each service is fully independent — its own dependencies, its own database, its own deploy:

project/
├── orders-service/
│   ├── main.py
│   ├── requirements.txt
│   └── Dockerfile
├── inventory-service/
│   ├── main.py
│   ├── requirements.txt
│   └── Dockerfile
└── docker-compose.yml

The inventory service

A minimal service exposing stock levels, with a Pydantic model defining the response shape:

# inventory-service/main.py
fromfastapiimportFastAPI, HTTPException
frompydanticimportBaseModel

app= FastAPI(title="Inventory Service")

stock= {"SKU-100": 42, "SKU-200": 0}

classStockLevel(BaseModel):
  sku: str
  quantity: int

@app.get("/stock/{sku}", response_model=StockLevel)
async defget_stock(sku: str):
  ifsku not in stock:
    raiseHTTPException(status_code=404, detail="SKU not found")
  returnStockLevel(sku=sku, quantity=stock[sku])

The orders service — calling inventory asynchronously

This is where the async advantage shows up — the orders service calls inventory over HTTP using httpx's async client, without blocking the event loop:

# orders-service/main.py
importhttpx
fromfastapiimportFastAPI, HTTPException
frompydanticimportBaseModel
importos

app= FastAPI(title="Orders Service")
INVENTORY_URL= os.getenv("INVENTORY_URL", "http://inventory:8000")

classOrderRequest(BaseModel):
  sku: str
  quantity: int

@app.post("/orders")
async defcreate_order(order: OrderRequest):
  async withhttpx.AsyncClient() as client:
    response= awaitclient.get(f"{INVENTORY_URL}/stock/{order.sku}")

  ifresponse.status_code == 404:
    raiseHTTPException(status_code=400, detail="Unknown SKU")

  available= response.json()["quantity"]
  ifavailable < order.quantity:
    raiseHTTPException(status_code=409, detail="Insufficient stock")

  return{"status": "confirmed", "sku": order.sku, "quantity": order.quantity}

Dockerfile (shared pattern for both services)

FROMpython:3.12-slim
WORKDIR/app
COPYrequirements.txt .
RUNpip install --no-cache-dir -r requirements.txt
COPY. .
CMD["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml

Docker Compose gives each service internal DNS resolution by name — that's why the orders service can reach inventory via http://inventory:8000 without any manual service discovery:

services:
  inventory:
    build:./inventory-service
    ports:
      - "8001:8000"

  orders:
    build:./orders-service
    ports:
      - "8000:8000"
    environment:
      INVENTORY_URL: http://inventory:8000
    depends_on:
      - inventory
💡Notice that depends_on only controls container start order, not readiness — if inventory takes longer to become responsive than orders, add a retry with backoff on the orders side rather than assuming inventory is ready the instant its container starts.

Run it with docker compose up --build, then test with curl -X POST localhost:8000/orders -H "Content-Type: application/json" -d '{"sku":"SKU-100","quantity":5}' — the orders service will call inventory internally and return a confirmed order.

Planning a microservices migration or a new API-first system? Whether FastAPI, Laravel, or .NET is the right fit depends entirely on your team and constraints — this is exactly the kind of decision I work through as part of web development projects.

Common questions

Why choose FastAPI over Flask or Django for microservices?
FastAPI is built on ASGI and native async/await, which matters a lot for microservices since they spend much of their time waiting on network calls to other services. Flask and Django are primarily WSGI-based and synchronous by default, making concurrent inter-service calls less efficient. FastAPI also generates OpenAPI docs automatically and validates request/response bodies via Pydantic, which is genuinely useful when many small services need clear, self-documenting contracts.
How do FastAPI microservices discover and communicate with each other?
For a small number of services, hardcoded URLs or environment variables pointing to each service's address are often enough, especially inside Docker Compose where service names resolve via internal DNS. As the system grows, a service registry (Consul) or a service mesh becomes worth the added complexity — but don't reach for those tools before you actually need them.
Should each FastAPI microservice have its own database?
Generally yes — this is one of the core principles of microservices architecture. Sharing a single database across services quietly reintroduces coupling, since services end up depending on each other's schema. Each service owning its data means services can evolve independently, at the cost of needing to handle data consistency across services deliberately, usually via events rather than direct database joins.
When should I NOT use microservices with FastAPI?
If your team is small, your domain model is still changing frequently, or you don't yet have real operational needs for independent scaling or deployment, a single well-structured FastAPI monolith is usually the better starting point. Microservices add real infrastructure and coordination overhead that only pays off once you've hit a genuine scaling or team-organization constraint.
Building an API-first system?
I build backend systems across FastAPI, Laravel, and .NET — matched to what your project actually needs.
Explore Web Development →
© Copyright 2024 Ajish Stephen