A monorepo for FastAPI services is less about code organization and more about managing dependencies and deployment pipelines as a single, cohesive unit.

Let’s see this in action. Imagine a simple setup with two FastAPI apps, user_service and product_service, sharing a common auth library.

monorepo/
├── services/
│   ├── user_service/
│   │   ├── app/
│   │   │   ├── __init__.py
│   │   │   └── main.py
│   │   ├── pyproject.toml
│   │   └── Dockerfile
│   └── product_service/
│       ├── app/
│       │   ├── __init__.py
│       │   └── main.py
│       ├── pyproject.toml
│       └── Dockerfile
├── shared/
│   └── auth/
│       ├── src/
│       │   ├── __init__.py
│       │   └── utils.py
│       └── pyproject.toml
└── pyproject.toml  # Root project configuration

In shared/auth/pyproject.toml:

[tool.poetry]
name = "shared-auth"
version = "0.1.0"
description = "Common authentication utilities"
authors = ["Your Name <you@example.com>"]
packages = [{include = "src"}]

[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.95.2"

In services/user_service/pyproject.toml:

[tool.poetry]
name = "user-service"
version = "0.1.0"
description = "User management service"
authors = ["Your Name <you@example.com>"]
packages = [{include = "app"}]

[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.95.2"
shared-auth = { path = "../../shared/auth", develop = true } # Local dependency

And services/product_service/pyproject.toml would be similar, also depending on shared-auth.

The root pyproject.toml might use a tool like Poetry or PDM for workspace management. With Poetry, it could look like this:

[tool.poetry]
name = "monorepo"
version = "0.1.0"
description = "Monorepo for FastAPI services"
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.10"

[tool.poetry.group.services]
optional = true

[tool.poetry.group.services.dependencies]
user-service = { path = "services/user_service", develop = true }
product-service = { path = "services/product_service", develop = true }

[tool.poetry.group.shared]
optional = true

[tool.poetry.group.shared.dependencies]
shared-auth = { path = "shared/auth", develop = true }

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

When you run poetry install at the root, it recognizes the workspace and installs all sub-dependencies, making shared-auth available to user_service and product_service as if it were a published package.

The primary problem this solves is dependency management across related services. Instead of publishing internal libraries, versioning them, and managing separate package registries, you can link them directly within the monorepo. This dramatically simplifies local development; changes to shared/auth are immediately reflected in user_service without any publishing steps.

Internally, tools like Poetry or PDM use symlinks or path-based dependencies to achieve this. When shared-auth is declared with path = "../../shared/auth", the build tool essentially makes the shared/auth directory available in the site-packages of the dependent projects. This allows Python’s import system to find shared.auth.utils directly.

The exact levers you control are the dependency declarations. By specifying shared-auth = { path = "...", develop = true }, you’re telling the package manager to treat shared/auth as a local, editable dependency. You can also manage different versions of Python, FastAPI, or other libraries across services if needed, though consistency is usually preferred.

Deployment pipelines become a key consideration. Tools like Bazel, Pants, or even custom shell scripts are used to build and deploy only the services that have changed, leveraging the monorepo structure for efficient CI/CD. This often involves analyzing the dependency graph to determine which components are affected by code modifications.

The common pitfall is treating a monorepo as a single, monolithic application. While dependencies are managed centrally, each service should still maintain its own independent build, test, and deployment lifecycle. A monorepo simply provides a more efficient way to manage the code and shared components that feed into these independent lifecycles.

The next concept you’ll grapple with is how to manage inter-service communication, such as REST API calls or asynchronous messaging, when they’re all housed in the same repository.

Want structured learning?

Take the full Fastapi course →