18 KiB
18 KiB
name, description
| name | description |
|---|---|
| nicegui | Build and scaffold production-ready NiceGUI + FastAPI app architecture. |
NiceGUI
Build a production-ready, multi-page NiceGUI application that follows modern FastAPI project layout and architecture practices.
Goal
Create a clean, maintainable app structure where:
- FastAPI is the underlying ASGI app.
- NiceGUI is mounted/initialized in a way that aligns with FastAPI best practices.
- Each page is implemented as a separate module in
pages/. - The project is easy to test, extend, and deploy.
Requirements
- Prefer Pydantic settings with a
.envexample. - Consider adding structured logging configuration.
- Use a
src/-style layout with clear package boundaries. - Use an app factory (
create_app) and avoid global side effects at import time. - Register NiceGUI pages from modules in
pages/. - Keep UI logic in page modules and shared UI helpers/components in a separate package.
- Include at least these pages:
- Home (
/) - Dashboard (
/dashboard) - About (
/about)
- Home (
- Include a health endpoint (
/healthz) on the FastAPI side. - Provide minimal test examples for FastAPI routes and page registration.
- Use FastAPI lifespan for startup/shutdown resource management.
Recommendations
- Consider a shared navigation/header component and left drawer used by all pages.
- Consider including dev tooling basics (formatting/lint/test commands).
- Prefer clear separation of concerns:
db/for engine/session/base/models/repositories (if needed)api/for HTTP endpointsui/for NiceGUI pages/componentsservices/for business logic
- If you need database access, a solid pattern is to use FastAPI + SQLAlchemy best practices:
- Use one engine per application process (do not create per request).
- Prefer a request-scoped session dependency using
yield. - Keep transaction boundaries explicit (commit/rollback) in service/repository flows.
- Avoid sharing a session across concurrent tasks.
- Consider production-minded pool settings (
pool_pre_ping=Trueand sensible recycle/timeout values). - Prefer Alembic for schema migrations.
Suggested Project Structure
Base structure (no database required):
.
├─ pyproject.toml
├─ .env.example
├─ README.md
├─ src/
│ └─ app/
│ ├─ __init__.py
│ ├─ main.py
│ ├─ config.py
│ ├─ logging.py
│ ├─ api/
│ │ ├─ __init__.py
│ │ └─ health.py
│ ├─ services/
│ │ ├─ __init__.py
│ │ └─ example_service.py
│ ├─ ui/
│ │ ├─ __init__.py
│ │ ├─ components/
│ │ │ ├─ __init__.py
│ │ │ └─ nav.py
│ │ └─ pages/
│ │ ├─ __init__.py
│ │ ├─ home.py
│ │ ├─ dashboard.py
│ │ └─ about.py
│ └─ bootstrap.py
└─ tests/
├─ test_health.py
└─ test_pages_registration.py
Conceptual boundaries for the base structure
main.py: application entrypoint only; wirescreate_app()and process startup concerns.bootstrap.py: app composition layer; owns app factory, router registration, page registration, and lifespan wiring.config.py: configuration boundary; centralizes settings parsing and typed config objects.logging.py: observability boundary; centralizes logging format/levels/handlers so modules do not configure logging ad hoc.api/: transport boundary for HTTP endpoints; validate/shape request-response objects and delegate business work toservices/.services/: use-case/business boundary; orchestrates domain behavior and dependencies, independent of UI rendering.ui/pages/: route-level UI boundary; one module per page, focused on layout/event handling for that page.ui/components/: reusable presentation boundary; shared UI widgets/composition utilities with no business side effects.tests/: behavior boundary; verify public behavior (health endpoint, page registration, service outcomes), not private implementation details.
Dependency direction to keep clear
- Prefer this dependency flow:
main/bootstrap->config/logging+api+ui/pages+servicesapi->servicesui/pages->ui/components+servicesservices-> pure helpers/clients (and laterdb/if enabled)
- Avoid reverse imports (for example,
servicesimporting fromui/pagesorapi). - Keep page modules and API handlers as thin adapters; keep business decisions in
services/.
Extending This Pattern with a Database (Optional)
If database access is needed, extend with:
.
├─ alembic.ini
├─ alembic/
│ ├─ env.py
│ └─ versions/
├─ src/
│ └─ app/
│ └─ db/
│ ├─ __init__.py
│ ├─ base.py
│ ├─ session.py
│ ├─ models/
│ │ ├─ __init__.py
│ │ └─ example.py
│ └─ repositories/
│ ├─ __init__.py
│ └─ example_repo.py
└─ tests/
└─ test_db_session_dependency.py
Recommended database layering
- Keep
db/focused on persistence primitives:session.py: engine + session factory + dependency helpersmodels/: ORM table mappings onlyrepositories/: query/persistence operations only
- Keep transaction orchestration in
services/, not in UI pages. - Keep
ui/pages/free of SQLAlchemy details; pages call services. - Keep API handlers thin and delegate data work to services/repositories.
Session and transaction pattern to request
- Use one engine and one sessionmaker per process, initialized at app startup.
- Use a
get_db_session()dependency withyieldfor request-scoped sessions. - In write flows, always use context managers for commit/rollback safety.
- In read-only flows, avoid unnecessary transactions and keep queries narrowly scoped.
- Do not share a session across concurrent tasks; open a new session per task/unit of work.
Migration and schema workflow
- Treat Alembic migrations as the source of truth for schema evolution.
- Prefer:
alembic revision --autogenerate -m "..."- review migration script
alembic upgrade head
- Avoid relying on
metadata.create_all()for production schema management. - Include a lightweight startup check that logs current migration state (without mutating schema).
Configuration and runtime concerns
- Read
DATABASE_URLfrom settings (pydantic-settings) and keep secrets out of source control. - Configure pool options appropriate for environment (for example:
pool_pre_ping, timeout/recycle tuning). - Keep SQL echo/debug logging disabled in production by default.
- Consider a readiness probe (
/readyz) that verifies DB connectivity when your deployment needs it.
Testing guidance for DB-enabled projects
- Unit test repositories against a disposable test database.
- Integration test
get_db_session()lifecycle and rollback behavior on failures. - Add migration tests that validate model changes are represented in Alembic revisions.
- Add API/service tests for critical read/write paths (including uniqueness and constraint errors).
- Keep DB tests isolated and deterministic (fresh schema + transactional cleanup per test module/session).
Extending This Pattern with LangGraph (Optional)
If your app needs multi-step AI workflows, tool-calling loops, or human-in-the-loop approvals, a clean extension is to add a dedicated LangGraph domain layer.
Recommended architecture shape
- Keep LangGraph logic out of
ui/pages/and out of HTTP route handlers. - Add an
ai/package (or similar) and keep graph definition, state schema, tools, and orchestration there. - Call the graph from
services/so your UI and API both use the same orchestration entry points. - Continue using FastAPI lifespan for shared resources (models, clients, stores/checkpointers).
Suggested project extension
.
└─ src/
└─ app/
├─ ai/
│ ├─ __init__.py
│ ├─ state.py # TypedDict / Pydantic state schemas + reducers
│ ├─ nodes/
│ │ ├─ __init__.py
│ │ ├─ llm.py
│ │ ├─ tools.py
│ │ └─ review.py # optional interrupt/human-review nodes
│ ├─ graphs/
│ │ ├─ __init__.py
│ │ └─ assistant_graph.py
│ ├─ runtime.py # compile() + checkpointer/store wiring
│ └─ contracts.py # input/output DTOs between services and graph
├─ services/
│ └─ ai_service.py # invoke/stream/resume wrappers
└─ api/
└─ ai.py # optional HTTP endpoints for run/stream/resume
Idiomatic LangGraph practices to request
- Define explicit graph state schema (
TypedDictor Pydantic) and use reducers for append/merge behavior where needed. - Compile graphs with persistence primitives appropriate to environment:
- dev/testing: in-memory checkpointer/store
- production: durable checkpointer/store
- Always pass a stable
thread_idinconfigurablefor resumable sessions and conversation continuity. - For interactive UX, prefer event streaming APIs and project-specific streaming adapters in service code.
- Use
interrupt()for human approvals/reviews; resume withCommand(resume=...)using the samethread_id. - Keep interrupt payloads JSON-serializable.
- Place non-idempotent side effects after interrupts (or isolate them in separate nodes), because interrupted nodes re-run from the start.
- If using tools, prefer LangGraph’s
ToolNode(or an equivalent centralized tool-execution node) for consistent execution/error handling. - Keep graph nodes deterministic and narrow in scope (single responsibility per node).
- Add observability with LangSmith tracing for graph runs, transitions, and latency debugging.
Integration guidance for NiceGUI + FastAPI
- UI pages should call
ai_servicemethods, not graph internals. - If the UX needs live token/progress updates, expose streaming from service -> UI in a transport-appropriate way.
- For approval workflows, surface interrupt payloads in UI, collect user response, then resume via service with
Command(resume=...). - Keep graph invocation boundaries typed (request/response contracts) so changes in graph internals do not leak into page modules.
Testing recommendations for LangGraph additions
- Unit test node functions as pure transformations where possible.
- Integration test graph routes (happy path, tool path, interrupt/resume path).
- Add tests ensuring
thread_idreuse resumes correctly and new IDs start fresh sessions. - Add regression tests for state-schema evolution and serialization compatibility.
Extending This Pattern with a Static Docs Site via Zensical (Optional)
If the app should also host a static documentation site, mount the generated docs output directory under a configurable route (default: /docs).
Recommended docs workflow
- Initialize docs in the project root:
uv run zensical new .
- Keep source markdown in Zensical's
docs_dir(default:docs/). - Build docs as part of release or startup pipeline:
uv run zensical build
- Serve the generated static output from Zensical's
site_dir(default:site/).
Config to include in app settings
docs_enabled: bool = truedocs_mount_path: str = "/docs"docs_site_dir: str = "site"- Optional for local/dev convenience:
docs_require_build: bool = false(if true, fail startup whensite/is missing)
FastAPI/NiceGUI integration pattern
- Keep docs mounting in the app composition layer (
bootstrap.py), not in page modules. - Use FastAPI static serving to mount the built directory (for example via
StaticFiles(..., html=True)). - Ensure
docs_mount_pathis normalized (leading slash, no trailing slash) and does not conflict with app/API routes. - If docs are disabled or build artifacts are missing, log a clear warning and continue (unless
docs_require_build=true).
Suggested project additions
.
├─ zensical.toml
├─ docs/
│ ├─ index.md
│ └─ ...
├─ site/ # generated by `uv run zensical build`
└─ src/
└─ app/
├─ config.py # docs_enabled/docs_mount_path/docs_site_dir
└─ bootstrap.py # mount static docs route
Deployment notes for docs mounting
- In CI/CD, run
uv run zensical buildbefore packaging/deploying the app image. - If
project.site_dirinzensical.tomlis changed, keepdocs_site_dirin app settings aligned. - If hosting behind a reverse proxy with a path prefix, verify docs links and static assets with the final external base path.
- Add a test asserting requests to
docs_mount_pathreturn the built index page when docs are enabled.
Implementation Notes
- Prefer an explicit page registration function pattern, for example:
register_pages()inui/pages/__init__.py- Each page module exports
register_page()
- Keep imports one-way to avoid circular dependencies.
- Keep page modules cohesive: route declaration + page layout for that route.
- Prefer FastAPI lifespan hooks where appropriate for startup/shutdown behavior.
- Prefer type hints throughout.
Styling architecture notes
- Prefer class-first UI composition for structure and layout using normal NiceGUI mechanics (
.classes(...)on containers/components). - Keep structural concerns in Python page/component modules (layout grids, spacing systems, responsive breakpoints, alignment).
- Keep cosmetic concerns in static CSS files (colors, gradients, shadows, border polish, typography fine-tuning, transitions).
- Avoid large inline
style(...)strings except for one-off dynamic values that must be computed at runtime. - Centralize CSS entrypoints in a small, predictable set (for example
ui/static/css/base.css,ui/static/css/theme.css,ui/static/css/components.css). - Include CSS once at app bootstrap/startup so pages share the same style contract; avoid per-page ad hoc includes.
- Prefer semantic class names for reusable patterns (
app-shell,page-section,card-surface) and utility classes only for local layout tweaks. - If using design tokens, define them as CSS custom properties in
:rootand reference them from component classes. - Keep page modules focused on structure and behavior; reusable visual patterns should live in shared UI components plus shared CSS.
Database-specific notes (only if your app needs a DB)
- Prefer settings via
pydantic-settingsand readDATABASE_URLfrom environment/dotenv. - Configure the SQLAlchemy engine once at app setup level; avoid creating engine/sessionmaker in endpoint functions.
- Provide
get_db_session()viayieldso sessions are consistently closed. - Prefer a repository/service boundary to keep SQL details out of page modules.
- Prefer Alembic for schema changes and include commands for
revision --autogenerateandupgrade head. - Avoid
metadata.create_all()in production startup flow; reserve it for optional local/dev bootstrap paths. - Consider a light DB readiness check in health reporting (or a dedicated
/readyz) when relevant. - Ensure logs do not leak secrets (for example, avoid unsafe SQL echo in production).
Output Format
The goal of the output is to produce an output
Return:
- A concise, high-level architecture explanation.
- An explanation of how the different parts of the concept will fit into this structure
- What are the core services?
- What are the main ui pages?
- What are the main ui components?
- Whether a database will be involved and whether the app owns the database
- Whether any AI will be used and what the workflow looks like
- A concrete implementation plan in the form of a checklist, organized into sections by concept which should roughly mirror the structure of sub-packages.
- Key functions/classes
- Configuration/settings available
- Migration or rollout notes (if relevant)
Guardrails
- Do not put all pages in a single file.
- Never use globals, implicit or otherwise.
- Keep code minimal but production-minded.
- Being clear and concise is a higher priority than being verbose.
- Prefer clarity and maintainability over clever abstractions.
Source Documentation (recommended references)
- FastAPI lifespan events:
- FastAPI settings and environment variables:
- FastAPI dependencies with
yield(session lifecycle pattern): - FastAPI SQL databases tutorial (when using a DB):
- SQLAlchemy engine configuration and pooling (when using a DB):
- SQLAlchemy session basics and lifecycle guidance (when using a DB):
- Alembic tutorial and migration environment (when using a DB):
- Pydantic settings management:
- NiceGUI pages/routing and FastAPI integration:
- NiceGUI security best practices:
- LangGraph overview:
- LangGraph quickstart:
- LangGraph workflows and agents:
- LangGraph persistence (checkpointer vs store):
- LangGraph memory concepts:
- LangGraph streaming:
- LangGraph interrupts / human-in-the-loop: