Quick start¶
Goal: run a tiny app where Streamlit shows data from a FastAPI route, both on one gateway URL: http://127.0.0.1:8000 by default.
Tip
Common gotcha: inside @app.page handlers, use client.get("/users"), not client.get("/api/users"). The client already points at the API prefix.
Install¶
pip install fluxlit
For FluxLit contributors working from a checkout, use an editable install instead:
git clone https://github.com/eddiethedean/fluxlit.git
cd fluxlit
pip install -e ".[dev]"
Note
Testing your app: start with FluxLitTestClient for API routes and Streamlit
AppTest for thin UI smoke checks. For multipage st.navigation, query params,
and apptest_select_page / apptest_assert_no_errors, see Testing and
Deep links and query parameters (includes a copy-paste Pytest recipe).
Scaffold (optional)¶
fluxlit new my-app
cd my-app
Minimal app¶
Create app.py:
from typing import Any
from fluxlit import FluxLit
from fluxlit.client import ApiClient
app = FluxLit(title="Admin Portal")
@app.api.get("/users")
def users():
return [{"name": "Ada"}]
@app.page("/")
def home(st: Any, client: ApiClient) -> None:
st.title("Dashboard")
st.write(client.get("/users").json())
Tip
0.9+: optional FastAPI-style page parameters (Depends, Annotated), typed query
strings with parse_query_params(), and per-run
PageMeta returns are documented in Streamlit pages: typing, Depends, and manifests.
0.10 improves async Depends when Streamlit already has an asyncio loop running, and adds
an opt-in gateway setting to forward an allowlist of browser header names on the HTTP hop
to Streamlit (see Configuration and Security architecture). Copy-paste recipes: Cookbook.
Runnable sample: examples/roadmap_09/ in the repository.
Run¶
From the directory that contains app.py:
fluxlit dev
# or, if your module path differs:
fluxlit dev app:app
Open the URL FluxLit prints. By default:
UI:
http://127.0.0.1:8000/API:
http://127.0.0.1:8000/api/usersOpenAPI:
http://127.0.0.1:8000/api/docsHealth:
http://127.0.0.1:8000/api/healthzReadiness:
http://127.0.0.1:8000/api/readyz
You can also run the same FluxLit object like a normal ASGI app:
uvicorn app:app --host 127.0.0.1 --port 8000
A :class:~fluxlit.app.FluxLit instance is an ASGI application: Uvicorn calls it directly, with no --factory and no FLUXLIT_APP env var required when your file is app.py and the variable is app. If your module is named differently, set target = "main:app" in fluxlit.toml, pass import_target="main:app" to :class:~fluxlit.app.FluxLit, or set FLUXLIT_APP.
Use a single Uvicorn worker (--workers defaults to 1). The unified stack fails lifespan startup when workers > 1 unless you set FLUXLIT_ALLOW_UNIFIED_UVICORN_MULTIWORKER=1 (unsupported — see Deployment).
Put gateway_port in fluxlit.toml or FLUXLIT_GATEWAY_PORT when the gateway is not on 8000, so the Streamlit sidecar can reach the API.
Advanced / legacy: uvicorn fluxlit.runtime:create_unified_app --factory with FLUXLIT_APP still works; prefer uvicorn app:app for clarity.
FluxLit looks for app:app by default. You can set target in fluxlit.toml or pyproject.toml under [tool.fluxlit] instead of typing it every time; see Configuration.
For structured per-request logs at the gateway (optional), set FLUXLIT_ENABLE_GATEWAY_ACCESS_LOG=1 and read Observability. The same page covers JSON log lines (fluxlit.logging), correlation (X-Request-ID end-to-end to Streamlit), and SLO / alerting sketches for healthz / readyz.
For local development, fluxlit dev --reload --reload-scope=full reloads the gateway and restarts Streamlit on changes; the default --reload-scope=gateway reloads FastAPI only. See Command-line interface.
Calling the API from Streamlit¶
ApiClient uses a base URL that includes /api (set as FLUXLIT_INTERNAL_API_BASE by the runtime). Use paths like client.get("/users"), not client.get("/api/users").
For Pydantic-validated JSON, use get_model() and post_model().
Secured routes (JWT and similar)¶
The client injected into @app.page handlers has no Authorization header. Use it for public endpoints or for logging in; for routes protected with JWTBearer (or your own dependency), create a client that adds the bearer on every request:
from typing import Any
from fluxlit.client import ApiClient
@app.page("/account")
def account(st: Any, client: ApiClient) -> None:
token = st.session_state.get("access_token")
if not token:
st.info("Sign in first.")
return
with ApiClient.for_fluxlit(bearer_token=token) as api:
st.write(api.get("/me").json())
Install fluxlit[auth] for JWT/OIDC helpers. Full patterns (env-driven make_jwt_bearer, OIDC BFF, prepare_streamlit_api_client, auth_header_factory) are in Auth recipes and Migrating to JWT and OIDC. A small runnable demo lives in the repo under examples/reference_auth/.
Project layout¶
my_app/
├── app.py
├── pkg/ # optional: discover_pages("pages", package="pkg")
│ ├── __init__.py
│ └── pages/
│ ├── __init__.py
│ └── reports.py # def register(app): ...
├── fluxlit.toml # optional CLI defaults
└── .env # secrets (do not commit)
See Configuration and Command-line interface for flags, env vars, and commands.
Next steps¶
Topic |
Doc |
|---|---|
Reverse proxies, subpaths, OAuth base URL |
|
Containers, probes, scaling |
|
TLS, HSTS, |
|
Secrets in logs, secret stores, key rotation |
|
Structured logs, JSON formatters, correlation, SLO notes, readiness details |
|
JWT / OIDC / Streamlit callers |
|
Typed Streamlit pages ( |
|
Markers, E2E, proxy smoke, |
|
Import errors, 503 readyz, API paths from Streamlit |