Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/adr/043-promo-video.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 043 — Promo video built with Remotion

Date: 2026-10-29

## Problem

FastAPI Startkit has no short visual introduction. We need a 60–95 second MP4 at 1920x1080 and 30fps, with no voiceover. It should show the framework's real APIs and real command output, and it must be easy to re-render whenever those change. The first cut showed `uv add fastapi-startkit` and `db:make:model`. The actual onboarding path is now `uvx fastapi-startkit@latest init` (or the curl/PowerShell installers) followed by `uv run python artisan db:install`. The cache module and AI agents are also missing from the video.

## Alternatives

- **Screen recording of a live editor and terminal.** It looks authentic, but it can't be reproduced, it's hard to edit, and it goes stale whenever the installer changes.
- **A video editor timeline (Kdenlive, After Effects).** The project files are binary, can't be reviewed in a PR, and need a GUI to re-render.
- **Motion Canvas.** It is code-driven like Remotion, but it has a smaller ecosystem and less mature headless rendering.
- **Remotion (React).** Scenes are TSX components, timing is set by frame numbers, and `npx remotion render` produces the MP4 headless.

## Decision

Use Remotion in a top-level `promo-video/` directory that sits outside the uv workspace. One `Promo` composition chains eleven scenes (93s) with `Series`:

1. Hook
2. Create a project
3. Interactive installer
4. Application
5. Routing
6. Database
7. Console
8. Cache
9. AI agents
10. Frontend
11. Call to action

Scenes are built from three primitives:

- `SceneHeading`: step label, title and caption.
- `CodeCard`: an editor window with a small Python/TS/Jinja highlighter. Lines are revealed in order.
- `ScrollTerminal`: a terminal made of timed blocks (typed commands, output lines, and prompt redraws). It shows only the last N rows, so long output scrolls like a real terminal.

Nothing on screen is invented:

- **Install commands** come from the docs site: `install.sh`, `install.ps1` and `uvx fastapi-startkit@latest init`.
- **Installer prompt boxes** in `src/installer-prompts.json` are recorded by driving the real `installer.prompts.Prompter` (`color=False, width=64`) through a scripted screen and keys. Each redraw is one frame of the prompt.
- **Installer plan and summary lines** are copied from a dry run of `fastapi-startkit init` that picks the web service, SQLite and React.
- **`bootstrap/application.py` and `HomeController`** are the installer-generated files.
- **Console output** is the real `db:install`, `db:make:migration` and `db:migrate` output.
- **Cache and AI snippets** were executed against the package. The AI snippet runs with `SupportAgent.fake`.

`Application(env=...)` is not shown because it is not part of the generated bootstrap.

## Implementation

- `promo-video/SCRIPT.md`: storyboard, per-scene timing and sources.
- `src/Promo.tsx`: the scene list with durations in seconds.
- `src/Root.tsx`: registers the composition at 1920x1080 and 30fps.
- `src/scenes.tsx`: scene content.
- `src/components.tsx`: primitives, with ligatures disabled so `==`, `&&` and `...` render literally.
- `src/highlight.tsx`: the highlighter.
- `npm run render -- --concurrency=4` writes `out/promo.mp4`, which is committed.

## Validation

- `npx tsc -p .` passes.
- `ffprobe` reports h264, 1920x1080, 30fps and 93s.
- `ffmpeg blackdetect` reports no black segments apart from the scene fade-outs.
- A still from every scene is checked for overflow and legibility.
1 change: 1 addition & 0 deletions docs/adr/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ Read this index first, then open the records relevant to the change. Before impl
| [037](037-http-exception-status-and-fastapi-title.md) | 2026-10-25 | HTTP exceptions keep their status; FastAPI title from config | `HTTPExceptionHandler` rendered every `HTTPException` and `ModelNotFoundException` as 500. Starlette `HTTPException` now goes through FastAPI's `http_exception_handler`, which keeps the status, the `detail` and the headers. A new dependency-free `fastapi_startkit.exceptions.HttpException` base (`get_status`/`get_response`/`get_headers`) is subclassed by `ModelNotFoundException`, `RouteNotFoundException`, `MethodNotAllowedException`, `AuthorizationException` and `ThrottleRequestsException` (429 with `Retry-After`), and is registered as its own FastAPI handler so it never reaches the re-raising `ServerErrorMiddleware`. `HTTPExceptionHandler.report` skips 4xx HTTP exceptions and still reports 5xx. `FastAPIConfig.title` reads `APP_NAME` (default `FastAPI Startkit`). `Application.__init__` no longer discards the FastAPI instance a provider creates in `register`. |
| [038](038-storage-binary-copy-and-request-scoped-vite-inertia.md) | 2026-10-25 | Storage binary reads and relative copy; request-scoped Vite and Inertia state | `LocalDriver.copy` resolves both paths through `get_path`. Local and S3 `get` read bytes and return `str` for UTF-8 content or raw `bytes` otherwise, and a new `get_bytes` (also on `StorageManager`/`Storage`) always returns bytes. `get` returns `bytes` for any non-UTF-8 content, including latin-1 text. `S3Driver.append` keeps the existing content. Vite `use_*_tag_attributes` accept dicts and `False`; the self-referencing lambda is gone. A pure ASGI `ViteMiddleware` (registered by `ViteProvider`) opens `Vite.request_scope()`, which holds the request's CSP nonce and preloaded assets in a per-instance `ContextVar` shared with child tasks. Outside a scope, the instance values are process-wide defaults, so a nonce set at startup still applies, and middleware inside the scope can read preloaded assets after `call_next`. `Inertia.set_root_view`/`Inertia.version` write to the current `InertiaRequestState` inside a request and to global defaults otherwise. |
| [041](041-database-install-command.md) | 2026-10-22 | InstallCommand base class and db:install | `console/install_command.py` adds an `InstallCommand` base class with a fluent class builder: `InstallCommand.register(name).copy(stub, dest).add_provider(Provider, "config.x.XConfig")`. Each call returns a new subclass. Providers and configs can be classes or dotted strings (strings for app-side configs and optional extras). `--force`, `publish_file` (skips existing files unless forced) and `register_provider` are shared. `register_provider` finds `Application(...)` in `bootstrap/application.py` with `ast` and inserts the entry and any missing imports at AST-reported positions, so formatting is kept. It handles `providers` passed by keyword or positionally. It does nothing if the provider is already listed, by bare name or attribute. It stops with manual steps and exit 1 if the bootstrap file is missing, or if a needed name is already imported from a different module (no aliasing). `db:install` (registered by `AppProvider`) subclasses it to add `--driver` and render `config/database.py` from the installer stub. After a successful run it prints next steps from the installer's database section (the `DB_*` env entries, `uv add` when SQLAlchemy is missing, migrations). Both live in `console/` so the optional SQLAlchemy extra is not imported. |
| [043](043-promo-video.md) | 2026-10-29 | Promo video built with Remotion | `promo-video/` (outside the uv workspace) renders a 93s, 1920x1080, 30fps MP4 from TSX scenes: hook, `uvx fastapi-startkit@latest init` / curl / PowerShell installers, the interactive installer, the generated bootstrap and providers, routing, ORM, `artisan db:install` → `db:make:migration` → `db:migrate`, cache, AI agents, Inertia + Vite, CTA. Every snippet and output line is real: prompt boxes are recorded from `installer.prompts.Prompter` into `src/installer-prompts.json`, installer and console lines come from actual runs, and cache/AI snippets were executed. `npm run render` writes the committed `out/promo.mp4`. |
5 changes: 5 additions & 0 deletions promo-video/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
out/*
!out/promo.mp4
!out/stills/
!out/thumbnail.png
45 changes: 45 additions & 0 deletions promo-video/SCRIPT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# FastAPI Startkit — Promo Script

Format: 1920x1080, 30fps, 155s, no voiceover. On-screen text, code cards, terminals and a browser window, over an original synthesized soundtrack (120 BPM, a half-time intro that builds to a drop at 0:36) (`music/generate.py` → `public/music.mp3`).

| # | Scene | Time | On screen |
|---|---|---|---|
| 1 | Python · FastAPI | 0:00–0:07 | "FastAPI makes the first endpoint easy" / "Async, typed and fast. One file gets you an API." A hello-world `main.py`, then "Then the app grows:" with chips: Logging, Environment & .env, Database, Tests, Frontend. |
| 2 | Sound familiar? · Logging | 0:07–0:16 | "A page of setup for one logger" / "And it still can't post to Slack." A stdlib `logging` setup in `app/logger.py` (formatter, `StreamHandler`, `FileHandler`), then three question chips: "Send errors to Slack?", "Add another channel?", "A new log file every day?" |
| 3 | Sound familiar? · Environment | 0:16–0:25 | "Where did my .env go?" `app/settings.py` with `load_dotenv()` and `os.getenv`, `tests/conftest.py` loading `.env.test` by hand. Questions: "DATABASE_URL is None?", "load_dotenv() in every file?", "Which .env do tests read?" |
| 4 | Indie hacker | 0:25–0:31 | "Are you an indie hacker?" / "You want full-stack batteries, without leaving FastAPI and Python." Chips: Async ORM & migrations, Artisan CLI, Testing, Vite + Inertia, AI agents. |
| 5 | Hook | 0:31–0:36 | "Introducing fastapi-startkit", then the headline "Full-stack productivity for FastAPI". Subtitle "A modular, provider-driven framework for Python." |
| 6 | 01 · Create a project | 0:36–0:42 | Three install cards. macOS / Linux: `/bin/bash -c "$(curl -fsSL https://fastapi-startkit.github.io/install.sh)"`. Windows: `powershell -ExecutionPolicy ByPass -c "irm https://fastapi-startkit.github.io/install.ps1 \| iex"`. With uv: `uvx fastapi-startkit@latest init`. |
| 7 | 02 · Interactive installer | 0:42–0:54 | "Start small: just the web app". Terminal running `uvx fastapi-startkit@latest init`. The real boxed prompts (`src/installer-prompts.json`) answer `my-app` and the Web service, move from the SQLite default up to None for the database, and keep None for the frontend. Then `Application: web; database: none; frontend: none`, the 24 `create ...` lines, `Dependency: fastapi-startkit[fastapi]==0.67.0`, the confirm prompt, "Your application is ready." and `Run: uv run python artisan serve`. |
| 8 | 03 · Run it | 0:54–1:02 | One terminal: `uv run python artisan serve` with `Starting Uvicorn server on 127.0.0.1:8000 [bootstrap.application:app]...` and the Uvicorn reload-mode startup lines. A browser window loads `127.0.0.1:8000` and shows the raw JSON `{"message":"Welcome to FastAPI Startkit"}`; the terminal logs `"GET / HTTP/1.1" 200 OK`. No `npm run dev`, because there is no frontend yet. |
| 9 | 04 · Application | 1:02–1:11 | "Everything is a provider". The generated `bootstrap/application.py` with only `(LogProvider, LoggingConfig)` and `(FastapiProvider, FastAPIConfig)`. Next to it, an `AppProvider(Provider)` with `register()` binding `billing` into the container and `boot()` resolving it. |
| 10 | 05 · Logging | 1:11–1:20 | "Channels, not handlers". `config/logging.py` with `stack` → daily, terminal and slack, plus a `slack` channel (`#alerts`, `SLACK_TOKEN`, `level="error"`). `app/orders.py` calls `Logger.info` and `Logger.error`; `tail -f storage/logs/2026-10-29.log` shows both lines with file and line number. |
| 11 | 06 · Routing | 1:20–1:28 | `routes/web.py`: `guest = Router()`, `auth = Router(dependencies=[Depends(auth)])`, `auth.resource("organizations", organizations_controller)`. The seven resource routes fan out using `{organization}` parameters. |
| 12 | 07 · Add a database | 1:28–1:40 | "One command adds a provider". Terminal: `uv run python artisan db:install` with its real output and next steps, then `uv add "fastapi-startkit[database,sqlite]"` with uv's resolve/install lines (`+ aiosqlite`, `+ faker`, `+ greenlet`, `+ sqlalchemy`). Beside it, `bootstrap/application.py` after the command, with the two new imports and `(DatabaseProvider, DatabaseConfig)` highlighted. |
| 13 | 08 · Model & migration | 1:40–1:52 | Terminal: `uv run python artisan db:make:model Post --migration` ("Model created: app/Post.py", "Migration file created: ..._create_posts_table.py"), then `db:migrate` ("Migrating:" / "Migrated: ... (0.00s)"). Cards: the generated migration with `table.string("title")` and `table.text("body")` added, and `app/Post.py` with the columns declared as annotated fields (`id: int`, `title: str`, `body: str`), as in the models docs. |
| 14 | 09 · Query it | 1:52–2:02 | `PostsController.index` returning `(await Post.all()).serialize()`, the `/posts` route in `routes/api.py`, and a browser at `127.0.0.1:8000/posts` showing the real JSON row. |
| 15 | 10 · Testing | 2:02–2:11 | "Tests get their own .env". `.env.testing` (`DB_DATABASE=databases/testing.sqlite`, `LOG_CHANNEL=terminal`) and `tests/test_posts.py` using `HttpTestCase` + `RefreshDatabase`; `uv run pytest -v -p no:warnings` shows both tests passing. |
| 16 | 11 · Frontend | 2:11–2:21 | "Add Vite + Inertia when you need them". `bootstrap/application.py` with the React installer's `InertiaProvider`, `ViteProvider` and `ViteConfig` imports and `(ViteProvider, ViteConfig)`, `InertiaProvider` highlighted after the existing providers. `npm run dev` with real Vite 8 + `fastapi-vite-plugin` output, and a browser rendering the React `Home.tsx` stub. |
| 17 | 12 · AI agents | 2:21–2:29 | `SupportAgent(Agent)` with `@provider("openai")`, `instructions()`, `tools()` and `schema()` returning a pydantic `OrderStatus`. Usage: `await SupportAgent().prompt(...)`, `response["structured_response"]` and `with SupportAgent.fake(['{"order_id": 1042, "status": "shipped"}'])`. |
| 18 | CTA | 2:29–2:35 | "Build your next FastAPI app faster." `uvx fastapi-startkit@latest init`, `fastapi-startkit.github.io` and `github.com/fastapi-startkit/fastapi-startkit-framework`. |

Sources:

- Installer output and generated files: a real `fastapi-startkit init --app web --database none --frontend none` run.
- Run it, Logging, Add a database, Model & migration, Query it and Testing: a real session in that generated app (`artisan serve`, `db:install`, `uv add`, `db:make:model Post --migration`, `db:migrate`, `curl /` and `/posts`).
- Frontend providers: the `bootstrap/application.py` that `init --frontend react` generates.
- Prompt boxes: recorded from `installer.prompts.Prompter` with `color=False, width=64`.
- `db:install` next steps: `console/install_command.py` and the database install command.
- Migration lines: the masoniteorm migration commands.
- AI snippet: executed against the package's fake.

## Render

```bash
cd promo-video
npm install
npm run render -- --concurrency=4
npm run thumbnail
```

`npm run thumbnail` writes the 1280x720 YouTube thumbnail to `out/thumbnail.png`.
54 changes: 54 additions & 0 deletions promo-video/YOUTUBE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# YouTube upload

## Title

fastapi-startkit: Full-Stack FastAPI in Minutes (ORM, Logging, Tests, Vite + Inertia, AI)

Alternatives:

- Stop Rewriting FastAPI Boilerplate: Meet fastapi-startkit
- Full-Stack FastAPI Without Leaving Python | fastapi-startkit
- From Hello World to Full-Stack FastAPI App in 2 Minutes

## Description

FastAPI makes your first endpoint easy. Then the app grows, and you need logging, .env handling, a database, tests and a frontend. fastapi-startkit gives you all of that without leaving FastAPI and Python.

It's a modular, provider-driven framework: start with a plain web app and add each feature as a provider when you need it.

Get started:
uvx fastapi-startkit@latest init

What's inside:
• Interactive installer: pick your app, database and frontend
• Providers: register and boot services in one place
• Logging channels: daily files, terminal and Slack, switched with LOG_CHANNEL
• Async ORM: models, migrations and seeders through the artisan CLI (db:install, db:make:model, db:migrate)
• Testing: .env.testing loads automatically, plus HttpTestCase and RefreshDatabase
• Frontend: Vite + Inertia with React and hot reload
• AI agents: typed agents with tools, structured output and fakes for tests

Chapters:
0:00 Why FastAPI apps get messy
0:31 Introducing fastapi-startkit
0:42 Interactive installer
0:54 Run it & providers
1:11 Logging & routing
1:28 Add a database
1:40 Models & migrations
1:52 Query & test
2:11 Vite + Inertia frontend
2:21 AI agents & get started

Links:
📖 Docs: https://fastapi-startkit.github.io
⭐ GitHub: https://github.com/fastapi-startkit/fastapi-startkit-framework
📦 PyPI: https://pypi.org/project/fastapi-startkit/

Music: original track, synthesized for this video.

#FastAPI #Python #WebDevelopment

## Tags

fastapi, python, fastapi-startkit, fastapi framework, python web framework, full stack python, async orm, sqlalchemy, fastapi tutorial, inertia, vite, react, python logging, pytest, ai agents, indie hacker
Loading
Loading