A reusable Copier template for InfinitiBit FastAPI backends. It scaffolds a two-tier modular monolith designed to shed pieces into independent microservices later — without a rewrite.
Extracting a service =
git mv src/services/<x>into a new repo + swapping one in-processpublic.pyimplementation for an HTTP client.
Every convention exists to make that sentence literally true — and the boundaries
that guarantee it are enforced by import-linter in CI, not left to code review.
You need uv (curl -LsSf https://astral.sh/uv/install.sh | sh).
You do not need to clone this repo — Copier pulls it for you.
# Generate a new project. --trust lets the template provision Python + deps for you.
uvx copier copy --trust gh:InfinitiBit/fastapi-service-template my-appCopier asks a few questions (see below), writes the project into my-app/, then
runs cp .env.example .env, uv python install (the project's pinned Python,
even if your system only has an older one), and uv sync. After it finishes:
cd my-app
uv run lint-imports # architectural boundaries (must be green)
uv run pytest # tests
uv run fastapi dev src/main.py # run the app → http://localhost:8000/docsThat's it — a working, boundary-enforced FastAPI app on the right Python.
Without
--trust, Copier still generates the project but skips those steps; it prints the commands (uv python install→uv sync) to finish setup by hand.--trustis required because Copier never runs template shell commands unless you opt in.
The project pins its Python in
.python-versionandrequires-python, so uv always selects (and installs) the correct interpreter — never the system one.
cd my-app
uvx copier update --trust # re-applies the template, keeping your editsThis is the payoff of Copier over a plain clone: kernel fixes and new lint rules propagate to every generated project.
| Question | Effect |
|---|---|
project_name |
Human-readable name → README, app title. |
project_slug |
Python package / distribution name (snake_case). |
python_version |
requires-python, ruff target, pyright. |
layout |
single-service vs modular-monolith — only tunes README framing; the bones are identical. |
first_module_name |
Names the first slice under src/services/. |
include_auth |
Scaffold platform/auth (a simple generic header-based auth). |
include_integration_example |
Scaffold an example integrations/ module. |
src/
kernel/ stateless infra — config, db, storage, broker, events, errors
platform/ PLATFORM CORE — always co-located (auth, registry); one DB, normal FKs
services/ SERVICE CANDIDATES — extractable slices; own tables + bucket + workers + agent
integrations/ INTEGRATION MODULES — external-system wrappers
api/ main.py composition root + app factory
Three archetypes, two tiers, one direction of dependency. The Shared Kernel holds stateless infra. The Platform Core is the identity/registry everything co-locates with (one DB, normal FKs — never split out). A Service Candidate is a self-contained domain that owns its tables, storage bucket, background workers, and optional AI pipelines, and is built to become its own service. Integration Modules each wrap one external system.
The rules (enforced by import-linter): dependency flows
services → platform → kernel; services never import each other; platform never
imports services; cross-module access is only ever through a module's public.py
(DTOs + a Protocol), never another module's models/service/repository.
The full architecture — the reasoning, the data-ownership rules, the slice
anatomy — is documented in the generated project's own README.md, and in this
repo's design docs:
CONTEXT.md— the canonical glossarydocs/adr/— the design decisions and rejected alternativesdocs/worked-example-gt-tax-agent.md— a real app mapped onto the template
copier.yml # the questions, post-gen tasks (uv sync), and config
template/ # the project source (Copier _subdirectory)
# .jinja files are rendered; everything else copied verbatim
# templated path names produce the named slice and the
# optional auth / integration dirs
CONTEXT.md # architecture glossary (about the template; not copied into projects)
docs/ # ADRs + worked example (about the template; not copied into projects)
README.md # this file
Generated projects intentionally do not inherit this repo's ADRs, glossary, or
worked example — they get a clean docs/adr/ seeded for their own decisions.
# Render the template to a throwaway dir and verify it stays green:
uvx copier copy --trust --defaults -d project_name="Demo" -d first_module_name="widgets" . /tmp/gen
cd /tmp/gen && uv run lint-imports && uv run pytestuv · ruff · pyright · pydantic v2 · async SQLAlchemy 2.0 · Alembic · taskiq · pytest + httpx · pre-commit · import-linter.