FastAPI Clean Architecture — Scaffold Skill
Generates a production-ready project with Clean Architecture enforced across all 4 layers. The tech stack (database, auth, cache) is configurable; the architecture is not — layer boundaries, naming rules, and DI patterns are always applied.
For auditing an existing project use /fastapi-clean-architecture-review. To activate rules in an existing session use /fastapi-clean-architecture-mode.
Tech stack flags
| Flag | Values | Default |
|---|---|---|
--db |
postgres, mongodb, sqlite |
postgres |
--auth |
jwt, oauth2, apikey |
jwt |
--cache |
none, redis |
none |
--no-docker |
flag | docker enabled |
Workflow
Make a todo list and work through each step sequentially.
Step 1 — Resolve tech stack
Parse all flags. Apply defaults for any flag not provided.
Step 2 — Create directory structure
<project-name>/
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── container.py
│ ├── config/settings.py
│ ├── domain/
│ │ ├── entities/
│ │ ├── enums/operation_results.py
│ │ └── repositories/
│ ├── application/
│ │ ├── services/
│ │ └── use_cases/
│ ├── infrastructure/
│ │ ├── auth/
│ │ ├── database/
│ │ │ └── models/__init__.py
│ │ └── repositories/
│ └── api/
│ ├── dependencies/
│ ├── routers/
│ └── schemas/
│ ├── base_schema.py
│ └── operation_schema.py
├── tests/
│ ├── application/use_cases/
│ └── api/routers/
├── pyproject.toml
├── .env.example
├── CLAUDE.md
└── AGENT.md
Add alembic/ + alembic.ini for postgres or sqlite. Add Dockerfile + docker-compose.yml unless --no-docker.
Step 3 — Generate shared files
Generate these regardless of stack. Use concise, idiomatic Python — no unnecessary comments.
operation_results.py: ThreeStrEnumclasses —CreateResult,UpdateResult,DeleteResult. Values:success,failure,concurrency_error,unique_constraint_erroron all three; addnot_foundtoUpdateResultandDeleteResult.base_schema.py: PydanticBaseModelsubclassAPIModelBasewithalias_generator=to_camelandpopulate_by_name=True.operation_schema.py: ThreeAPIModelBasesubclasses —CreateOperationResponse(id: int | None),UpdateOperationResponse(message: str),DeleteOperationResponse(message: str).result_status_maps.py: Three functions —create_response,update_response,delete_response— each mapping the corresponding result enum to aJSONResponsewith the correct HTTP status code (201 success-create, 200 success-update/delete, 404 not-found, 409 conflict, 500 failure).
Step 4 — Generate database layer
postgres or sqlite
Four files following the SQLAlchemy 2.0 async session pattern:
connection_factory_base.py(application layer): abstractget_session()async context manager andclose().connection_factory.py(infrastructure): createsAsyncEngine+async_sessionmaker.get_session()checks aContextVar[AsyncSession | None]called_active_session— yields the active session if set, otherwise opens a new one with its own commit/rollback.transaction_manager_base.py(application layer): abstractbegin_transaction()async context manager.transaction_manager.py(infrastructure): injectsConnectionFactoryBase;begin_transaction()sets_active_sessionContextVar, delegates to the connection factory, handles commit/rollback.base.py: SQLAlchemyDeclarativeBasesubclass.
Driver: asyncpg for postgres (postgresql+asyncpg://), aiosqlite for sqlite (sqlite+aiosqlite:///).
mongodb
mongo_client_base.py(application layer): abstractget_database().mongo_client.py(infrastructure): wrapsmotor.motor_asyncio.AsyncIOMotorClient. No session or transaction manager needed.
No Alembic for MongoDB.
Step 5 — Generate auth layer
jwt
PasswordHasherBase/PasswordHasher— bcrypt via passlib.TokenServiceBase/TokenService— python-jose; issues access + refresh JWTs from settings.UserContextBase/UserContext— request-scoped;populate()raisesRuntimeErroron second call; scalar properties only (user_id,role).jwt_dependency.py— decodes JWT, callsuser_context.populate(), returnsTokenClaimsDTO.- Auth use case + DTO + routes under
src/application/use_cases/auth/andsrc/api/routers/auth/.
oauth2
OAuthServiceBase/OAuthService— exchanges provider token viahttpx.- Keep
UserContextBase/UserContextand JWT guard for internal session tokens issued after OAuth exchange. - No
PasswordHasher.
apikey
APIKeyServiceBase/APIKeyService— validates key against DB.- Guard in
src/api/dependencies/api_key_dependency.py. - No
UserContextBaseunless user identity is needed.
Step 6 — Generate cache layer (redis only)
CacheServiceBase(application layer): abstractget(),set(),delete().RedisService(infrastructure):redis.asyncioclient, singleton scope,close()method.- Bind as singleton in
container.py; callclose()inlifespanshutdown.
Step 7 — Generate settings.py
pydantic-settings BaseSettings. Include only fields for the resolved stack:
- Always:
APP_NAME,DEBUG - postgres/sqlite:
DATABASE_URL,IS_SQL_ECHO_ENABLED: bool = False - mongodb:
MONGODB_URL,MONGODB_DB_NAME - jwt/oauth2:
SECRET_KEY,ACCESS_TOKEN_EXPIRE_MINUTES = 30,REFRESH_TOKEN_EXPIRE_DAYS = 7 - redis:
REDIS_URL
Step 8 — Generate main.py
Key ordering requirement — strictly in this order:
- Create
Injector([AppModule()]) - Create
FastAPI(lifespan=lifespan) app.add_middleware(InjectorMiddleware, injector=injector)attach_injector(app, injector)app.include_router(...)for each router
lifespan shutdown block closes all singletons that have a close() method.
Step 9 — Generate container.py
injector.Module subclass AppModule with a configure(binder) method. Bind every Base/implementation pair. Use singleton scope only for ConnectionFactory, external service clients, and RedisService.
Step 10 — Generate pyproject.toml
Base dependencies: fastapi>=0.115, fastapi-injector>=0.6, injector>=0.22, pydantic>=2.0, pydantic-settings>=2.0, uvicorn[standard]>=0.30.
Stack additions:
| Stack | Extra |
|---|---|
| postgres | sqlalchemy[asyncio]>=2.0, asyncpg>=0.29, alembic>=1.13 |
| sqlite | sqlalchemy[asyncio]>=2.0, aiosqlite>=0.20, alembic>=1.13 |
| mongodb | motor>=3.4 |
| jwt | python-jose[cryptography]>=3.3, passlib[bcrypt]>=1.7 |
| oauth2 | httpx>=0.27, python-jose[cryptography]>=3.3 |
| redis | redis[asyncio]>=5.0 |
Dev: pytest>=8.0, pytest-asyncio>=0.23, httpx>=0.27, ruff>=0.4.
[tool.ruff] with line-length = 80. [tool.pytest.ini_options] with asyncio_mode = "auto".
Step 11 — Copy architecture docs
Copy CLAUDE.md and AGENT.md verbatim from FastAPI/API_PostgressDB/ into the new project root.
Step 12 — Generate .env.example
Only variables for the resolved stack with placeholder values. No real secrets.
Step 13 — Validate
uv run ruff check src/ --fix && uv run ruff format src/
Step 14 — Summary
Report: project name and path, resolved stack, file count by layer, ruff result, and next steps (install deps, copy .env, run migrations if applicable, start with uvicorn src.main:app --reload).
Source: joe-vi/Templates — distributed by TomeVault.