A Full-Stack Admin Panel’s Startup Sequence, from CLI to Dynamic Route Injection
A startup sequence that gets environment loading, plugin discovery, and route injection order wrong produces silent misconfigurations — wrong DB credentials, missing routes, or cached stale settings. This walkthrough surfaces the exact ordering constraints and the factory-plus-lifespan pattern that prevents those failures in production.
The backend boot sequence starts with a Typer CLI command that sets the environment and clears a pydantic-settings cache to force re-reading the correct .env file. Uvicorn then launches a factory-built FastAPI app, which assembles middleware, registers built-in routers, and runs a DynamicRouter that scans `backend/app/plugin/module_*/**/controller.py` files, mapping each `module_xxx` directory to a `/xxx` route prefix with deduplication and failure isolation. The lifespan context manager seeds the database, connects Redis, warms caches, and starts APScheduler before printing a readiness panel.
On the frontend, Vite loads environment variables and auto-imports Vue APIs and Element Plus components. The entry script enforces a cascading style order: Element Plus base, Tailwind utilities, then project SCSS overrides. Plugin initialization follows a hard dependency chain — Pinia store first, then Vue Router with hash-mode history, permission directives, global error handlers, and finally editor/i18n modules. Route guards intercept navigation: unauthenticated users are redirected to login, while authenticated users trigger an API call to fetch the backend menu tree, which `MenuProcessor` converts into Vue routes and injects via `router.addRoute` before re-triggering navigation.
The original code had a timing bug where environment variables were set after settings were already loaded, causing the system to silently use defaults — a class of error that’s easy to miss in integration tests but breaks per-environment configs.
Using a factory pattern with Uvicorn’s `factory=True` is underdocumented in the FastAPI ecosystem; this project shows it working in practice with hot-reload and multi-process deployments.
The DynamicRouter’s decision to isolate failures per plugin — logging the trace and continuing — is a deliberate robustness trade-off: a broken plugin won’t take down the entire admin panel, which matters in multi-tenant or plugin-market scenarios.
Frontend route injection from a server-side permission tree means the SPA has no compiled-in route map for protected pages; the entire navigation structure is a runtime artifact, which complicates code-splitting and static analysis but centralizes access control.