A CLI tool to scaffold production-ready FastAPI projects instantly — inspired by create-next-app, powered by Jinja2 templates and interactive prompts.
Project description
faststrapy
A CLI tool to scaffold production-ready FastAPI projects instantly — inspired by create-next-app, powered by Jinja2 templates and interactive prompts.
Answer a handful of prompts (or pass flags and skip them entirely) and get a working FastAPI project: settings management, an optional SQLAlchemy + Alembic database layer, structured logging, a health-check route, git init, dependency install, and code formatting — all done for you.
$ faststrapy create-app
----------------------------------------------------
Generating `my-service` at /home/anoop/my-service
→ project metadata
→ project structure
→ settings + .env
→ database layer
→ logger
→ health route
→ main.py entrypoint
→ alembic
Running post-generation steps...
→ git init
→ ensure uv installed
→ dependency install (uv sync)
→ format with black
----------------------------------------------------
Done. Next steps:
----------------------------------------------------
cd /home/anoop/my-service
# activate the virtual environment uv created
# (optional — `uv run` below works without activating)
# Windows (cmd): .venv\Scripts\activate
# Windows (PowerShell): .venv\Scripts\Activate.ps1
# macOS / Linux: source .venv/bin/activate
# run the project (from the project root)
uv run python -m app.main
# once it's running:
http://127.0.0.1:8000
http://127.0.0.1:8000/docs (interactive Swagger UI)
http://127.0.0.1:8000/redoc (ReDoc API reference)
----------------------------------------------------
Table of contents
- Requirements
- Installation
- Quick start
- CLI reference
- What gets generated
- How the prompts work
- Post-generation steps
- Running the generated project
- Project architecture (for contributors)
- Contributing
- Roadmap / known limitations
- License
Requirements
- Python 3.10+ (the generated projects default to 3.11, configurable per-project down to 3.10 during the prompts)
uv— not strictly required to install faststrapy itself, but the post-generation step uses it to install the new project's dependencies. If it's missing, faststrapy installs it for you automatically (see Post-generation steps).git— used for the automaticgit init+ first commit after scaffolding. Not required to run faststrapy itself, but that step is skipped with a warning if git isn't onPATH.
Installation
# with uv (recommended)
uv add faststrapy
# or run it once without installing, always pulling the latest version
uvx faststrapy create-app
# or with plain pip
pip install faststrapy
Once installed, the faststrapy command is on your PATH. There's no separate "run" step and no npm-style @latest suffix on individual commands — uv add / pip install pin the version at install time, and faststrapy create-app always runs whichever version you currently have installed. Upgrade with uv add faststrapy --upgrade or pip install -U faststrapy.
Quick start
Fully interactive — no flags, faststrapy asks you everything (project name, framework, Python version, and whether to use the recommended defaults or customize every option):
faststrapy create-app
Non-interactive — pass any flag and all prompts are skipped; anything you don't pass falls back to a recommended default:
faststrapy create-app --project-name my-service --python 3.12 --sync-type async
Scaffold into a specific directory:
faststrapy create-app --project-name my-service --path ./services/my-service
Skip git init / uv sync / black formatting (just generate the files):
faststrapy create-app --project-name my-service --skip-postgen
CLI reference
faststrapy create-app
| Flag | Type | Description |
|---|---|---|
--project-name |
str |
Name of the project. Also becomes the output folder name if --path isn't given, and the default .env variable prefix. |
--template |
str |
Framework to scaffold. Currently only fastapi is implemented (see Roadmap). |
--python |
float |
Target Python version for the generated project, e.g. 3.12. |
--sync-type |
str |
sync or async — controls both the database session style and the Alembic migration engine mode. |
--path |
str |
Output directory. Defaults to ./<project-name> in the current working directory. |
--skip-postgen |
flag | Skip git init, the uv/uv sync step, and Black formatting. Just write the files. |
Passing any of --project-name, --template, --python, --sync-type, or --path skips the interactive prompts entirely for that run — faststrapy fills in the rest with the recommended defaults (see _default_prompt_config). Pass none of them and you get the full interactive flow, including the "customize every option" path.
faststrapy logs
faststrapy logs [--upgrade] [--downgrade]
Reports where a generated project's logs are configured to go (file vs. database), based on the use_logs / save_logs_db choices made at scaffold time.
What gets generated
A default run (recommended settings, database + Alembic + logging + Black all enabled) produces:
my-service/
├── app/ # ← everything lives here, not the project root
│ ├── __init__.py
│ ├── main.py # FastAPI() app instance + entrypoint
│ ├── core/
│ │ ├── config/
│ │ │ └── settings.py # pydantic-settings Settings, reads .env
│ │ ├── database/ # only if a database was selected
│ │ │ ├── base.py
│ │ │ ├── connection.py
│ │ │ ├── async_session.py # (or sync_session.py)
│ │ │ ├── dependency.py
│ │ │ └── module_registry.py
│ │ └── log/ # only if logging was enabled
│ │ └── logger.py
│ ├── middlewares/
│ ├── utils/
│ └── modules/
│ ├── routes/
│ │ └── health_route.py # GET /health, always generated
│ └── models/ # only if a database was selected
├── alembic/ # only if Alembic was enabled
│ ├── env.py
│ ├── script.py.mako
│ └── versions/
├── alembic.ini
├── .env # real values, gitignored
├── .env.example # same keys, committed
├── .gitignore
├── .python-version
├── pyproject.toml # dependencies resolved from your choices
├── requirements.txt # same deps, plain pip-installable format
└── README.md
Notes on a few of the choices baked into this layout:
main.pylives insideapp/, not the project root. The app is meant to be run as a module —uv run python -m app.main— which keepsapp.core...-style absolute imports working correctly. Running it as a bare script (python app/main.py) would break those imports, since Python only adds the script's own directory tosys.path, not the project root.- Both
pyproject.tomlandrequirements.txtare generated, with matching dependency lists, so the project is installable either theuv/PEP 621 way or the classicpip install -r requirements.txtway. SERVER_PATHin.envis set toapp.main:app(or<your-folder-name>.main:appif you renamed the holder folder) — this is the import stringuvicorn.run()uses internally, and it's kept in sync with wherevermain.pyactually is.
How the prompts work
- Pre-config — project name, framework (
fastapi;flask/djangoare recognized but not yet implemented — see Roadmap), Python version, and whether the app's code should live inside a subfolder (default: yes,app/). - You're then asked: use the recommended defaults, or customize?
- Recommended defaults: Pydantic ✓, SQLAlchemy ORM ✓, Postgres (both local + Neon-ready) ✓, async DB access, Alembic ✓ (async), logging ✓ (console only), Black ✓.
- Customize: one prompt per option — database on/off, which database, sync vs async, ORM on/off, env var prefix, Alembic on/off, logging on/off (and whether to persist to a file), Black on/off.
All of this is captured in ProjectConfigSchema (PreConfig + DefaultConfig), which is what every generator function receives.
Post-generation steps
After the files are written, faststrapy runs (unless --skip-postgen):
git init— initializes a repo and creates the first commit (chore: scaffold project with faststrapy). Skipped with a warning ifgitisn't installed, or if your globalgitidentity (user.name/user.email) isn't configured yet — the commit will fail but nothing else is affected.uvauto-install — ifuvisn't already on yourPATH, faststrapy runs the official installer for your OS (thecurl | shone-liner on macOS/Linux, theirm | iexone on Windows) so the next step can succeed. This is best-effort: if it fails (offline, restricted permissions, unsupported shell), you get a one-line notice and the run continues — nothing crashes.uv sync— installs the generated project's dependencies into a fresh.venv.- Black formatting — if you kept Black enabled, the generated code is formatted in place, pinned to the project's target Python version.
Every post-generation step is independently best-effort: if one fails, you get a ⚠ skipped (<reason>) line and the rest still run. Nothing about a failed post-gen step blocks the "Done" summary at the end.
Running the generated project
cd my-service
# optional — uv run works without activating a venv at all
source .venv/bin/activate # macOS / Linux
.venv\Scripts\activate # Windows (cmd)
.venv\Scripts\Activate.ps1 # Windows (PowerShell)
uv run python -m app.main
Then visit:
http://127.0.0.1:8000— the apphttp://127.0.0.1:8000/docs— Swagger UIhttp://127.0.0.1:8000/redoc— ReDoc
Host, port, and reload behavior are all controlled by the generated .env (SERVER_HOST, SERVER_PORT, SERVER_RELOAD) via app/core/config/settings.py.
Project architecture (for contributors)
faststrapy has three moving pieces, each with its own registry pattern:
faststrapy/
├── main.py # Typer app entrypoint — registers sub_command
├── prompts.py # All interactive prompt logic (FaststrapyPrompts)
├── schemas/
│ └── project_config.py # PreConfig, DefaultConfig, ProjectConfigSchema — the
│ # single object passed to every generator/postgen step
├── configs/
│ └── commands.py # `create-app` and `logs` Typer commands; wires
│ # prompts → generators → postgen → summary output
├── generators/ # Each file writes one slice of the new project
│ ├── registry.py # @register_generator("name", order=N) decorator
│ ├── structure.py # order=10 — folder skeleton + __init__.py files
│ ├── project_meta.py # order=5 — pyproject.toml, requirements.txt, .gitignore, README
│ ├── core_config.py # order=20 — settings.py, .env, .env.example
│ ├── core_database.py # order=30 — database layer (conditional)
│ ├── logging_gen.py # order=40 — logger.py (conditional)
│ ├── routes.py # order=50 — health_route.py
│ ├── entrypoint.py # order=60 — main.py
│ ├── alembic_gen.py # order=70 — alembic/ (conditional)
│ └── fs_utils.py # write_file() / touch_init() helpers
├── postgen/ # Runs after files exist on disk
│ ├── registry.py # @register_postgen("name", order=N), catches
│ # exceptions per-step (best-effort by design)
│ └── actions.py # git init → ensure uv installed → uv sync → black
├── templates/ # .jinja templates rendered by the generators
│ └── *.py.jinja
└── utils/
└── jinja_env.py # Environment + render_template()
How the registries work: a generator (or postgen step) is just a function decorated with @register_generator("human readable name", order=N). run_generators() / run_postgen() sort by order and run them in that sequence, printing the name as they go. Lower order runs first. There's no other wiring needed — importing the module (already done for you in generators/__init__.py / postgen/__init__.py) registers it.
Adding a new generator
- Create
faststrapy/generators/your_thing.py. - Write a function
def generate_your_thing(base_path: Path, config: ProjectConfigSchema) -> None:, decorated with@register_generator("your thing", order=N). PickNbased on where it needs to run relative to the existing steps (e.g. afterstructureat 10, beforeentrypointat 60 if it needs to exist beforemain.pyreferences it). - If it's conditional (like the database layer), guard it:
if not config.default_config.<your_flag>: return. - If it writes a new field, add it to
DefaultConfiginschemas/project_config.pyfirst, and wire a prompt for it inprompts.py(both the customize path and, if it should have a sensible default,_default_prompt_config). - Add the import to
faststrapy/generators/__init__.py's import list so the decorator actually runs. - If it renders a template, add the
.jinjafile underfaststrapy/templates/and callrender_template("your_thing.py.jinja", **ctx).
Adding a new postgen step
Same pattern in faststrapy/postgen/actions.py, decorated with @register_postgen("name", order=N). Steps here are expected to shell out (git, uv, black, etc.) — raise a plain RuntimeError with a clear message on failure; the registry catches it and prints ⚠ skipped (<message>) without stopping the rest of the run. Don't add a try/except inside your own step unless you need to do something other than fail — that's already handled centrally.
Files you'll typically touch
| Change you want to make | File(s) |
|---|---|
| New scaffolding option / prompt | schemas/project_config.py, prompts.py, the relevant generator |
| New file type in generated projects | New file in generators/, new .jinja in templates/ |
| Change what recommended defaults are | prompts.py → _default_prompt_config |
Change CLI flags on create-app |
configs/commands.py |
| New post-generation shell step | postgen/actions.py |
| Fix/change a generated file's content | The relevant .jinja file in templates/ — not a .py file, these are text templates |
Files to be careful changing (or avoid changing without discussion)
pyproject.toml[project.scripts]and[build-system]— this is what makespip install faststrapyproduce a workingfaststrapycommand at all. Breaking the entry point (faststrapy.main:app) or thepackages = ["faststrapy"]wheel config breaks every install.- The
faststrapy/package name itself — this must match the PyPI project name and the console-script entry point. Don't rename or nest it further without updatingpyproject.tomlto match. generators/registry.py/postgen/registry.py— the ordering/execution engine both generator sets rely on. Changing its behavior (e.g. making generator failures best-effort like postgen's) is a real design decision, not a small tweak — raise it in an issue/PR description rather than changing it silently.SERVER_PATHintemplates/env.py.jinjaand whereentrypoint.pywritesmain.py— these two have to agree with each other (see What gets generated). If you movemain.py's output location, updateSERVER_PATHto match, and vice versa.templates/script_mako.py.jinja— despite the.jinjaextension (for consistency with the rest of the folder), this is copied verbatim, not rendered through Jinja — it uses Mako's${...}syntax, consumed by Alembic itself atalembic revisiontime. Don't run it throughrender_template().
Contributing
-
Fork the repo, then clone your fork.
-
Set up a dev environment:
uv sync uv pip install -e .
This installs faststrapy in editable mode so
faststrapy create-app(oruv run faststrapy create-app) reflects your local changes immediately. -
Branch naming — prefix by intent, short and hyphenated:
feature/<short-description>— new generator, new prompt, new flagfix/<short-description>— bug fixesdocs/<short-description>— README/docs-only changeschore/<short-description>— packaging, CI, dependency bumps, refactors with no behavior change
e.g.
feature/flask-template,fix/alembic-sync-mode,docs/contributing-section. -
Commit messages — Conventional Commits style is preferred:
feat: add flask template support,fix: correct SERVER_PATH for renamed app folder,docs: expand CLI reference. Not strictly enforced, but it's what the existing history follows and keeps changelogs generatable later. -
Test your change manually before opening a PR — there's no automated test suite yet (see Roadmap), so the working smoke test is:
uv run faststrapy create-app --project-name smoke-test --path /tmp/smoke-test cd /tmp/smoke-test uv run python -m app.main # confirm it boots and /docs loads
Run this with both the recommended-defaults path and, if your change touches a conditional generator (database/logging/alembic), the customize path with that option toggled off, to make sure nothing assumes it's always on.
-
Open the PR against
main, describe what changed and why, and mention which generator/postgenordervalues (if any) you added or touched, since ordering bugs are the easiest thing to introduce silently.
Reporting issues
Open a GitHub issue with: the exact faststrapy create-app command you ran (flags included), your OS and Python version, and the full output. If it's about the generated project rather than the CLI itself, include the relevant generated file.
Roadmap / known limitations
--template flaskand--template djangoare recognized by the prompts but explicitly rejected as "still in progress" — onlyfastapiis implemented today.- No automated test suite yet — contributions here are especially welcome (a good first one: a pytest suite that runs
create_appprogrammatically against a temp dir for each generator combination and asserts the expected files exist). faststrapy logscurrently only reports the configured log destination; it doesn't yet manage log files/DB rows for an existing project.- The
uvauto-install postgen step shells out to the official installer scripts (astral.sh/uv/install.sh/.ps1) at run time — if your network blocks that domain, the step fails gracefully but you'll need to installuvyourself.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file faststrapy-0.1.0.tar.gz.
File metadata
- Download URL: faststrapy-0.1.0.tar.gz
- Upload date:
- Size: 57.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e880c571461c48c58b50a145e7ae2b235f52f156649e0aa71a034af11362a77
|
|
| MD5 |
d602d5e79f74c897e8a057330f158a4b
|
|
| BLAKE2b-256 |
748dbd0a7e134bdb0459e180a5237b74fcd4cce1f4749d9678775734918e0dd1
|
Provenance
The following attestation bundles were made for faststrapy-0.1.0.tar.gz:
Publisher:
python-publish.yml on AnoopGeorge418/faststrapy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
faststrapy-0.1.0.tar.gz -
Subject digest:
0e880c571461c48c58b50a145e7ae2b235f52f156649e0aa71a034af11362a77 - Sigstore transparency entry: 2339766846
- Sigstore integration time:
-
Permalink:
AnoopGeorge418/faststrapy@58388fd5ca126db613c55062d38c2504413dd63b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/AnoopGeorge418
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@58388fd5ca126db613c55062d38c2504413dd63b -
Trigger Event:
release
-
Statement type:
File details
Details for the file faststrapy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: faststrapy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae6063c1845fb2ee8c1b14ea4ffe891292320600feba021fac9f9717296110f7
|
|
| MD5 |
77d04bbe0a2534406f6d0f81f20e2ad3
|
|
| BLAKE2b-256 |
72bae59d7a636374f3b82cfc915dad445de61c6174c637e41b9bb6271120d157
|
Provenance
The following attestation bundles were made for faststrapy-0.1.0-py3-none-any.whl:
Publisher:
python-publish.yml on AnoopGeorge418/faststrapy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
faststrapy-0.1.0-py3-none-any.whl -
Subject digest:
ae6063c1845fb2ee8c1b14ea4ffe891292320600feba021fac9f9717296110f7 - Sigstore transparency entry: 2339766857
- Sigstore integration time:
-
Permalink:
AnoopGeorge418/faststrapy@58388fd5ca126db613c55062d38c2504413dd63b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/AnoopGeorge418
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@58388fd5ca126db613c55062d38c2504413dd63b -
Trigger Event:
release
-
Statement type: