跪拜 Guibai
← Back to the summary

A Full-Stack Admin Panel’s Startup Sequence, from CLI to Dynamic Route Injection

In FastapiAdmin, the startup phase determines the correct execution of dependency injection, configuration loading, resource initialization, and security policies. The project builds its backend service on FastAPI + SQLAlchemy + Redis + APScheduler, while the frontend uses the Vue 3 + Vite + TypeScript + Pinia + Vue Router + Element Plus tech stack.

image.png

1. Overall System Startup Architecture Overview

image.png

2. Backend Startup Process in Detail (FastAPI)

The backend entry point is backend/main.py, working with backend/app/init_app.py to complete the assembly of the entire service and asynchronous resource initialization.

image.png

2.1 CLI Command Parsing and Configuration Loading

  1. Command-line startup: Running uv run main.py run --env=dev triggers the Typer command-line tool.

  2. Environment isolation and dynamic reading:

    • Sets the ENVIRONMENT environment variable to dev or prod.

    • Calls get_settings.cache_clear() to clear the cache, triggering pydantic-settings to dynamically read the corresponding .env.dev or .env.prod configuration file.

    • Validates server port, CORS whitelist, JWT secret key, database connection pool, and Redis parameters.

Note: The original system had an issue with environment variable setup; due to incorrect timing of calling settings, the set environment variables did not take effect and the system would start with default settings. This tutorial is written based on the corrected flow.

2.2 Uvicorn Service Bootstrap and Application Factory Pattern

    uvicorn.run(  
        app="main:create_app",  
        host=current_settings.SERVER_HOST,  
        port=current_settings.SERVER_PORT,  
        reload=env.value == EnvironmentEnum.DEV.value,  
        factory=True,  
        log_config=None,  
        timeout_graceful_shutdown=5,  
    )

2.3 Exception, Middleware, and Router Assembly (create_app)

Inside create_app(), component assembly is completed in a strict hierarchical order:

  1. Create FastAPI instance: Pass in basic metadata and the lifespan asynchronous context manager.

  2. register_exceptions(app): Uniformly captures custom business exceptions (such as expired credentials, insufficient permissions, data validation exceptions) and standardizes JSON responses.

  3. register_middlewares(app): Assembles middleware in reverse order chain (CORS cross-origin, full-chain Request ID / Trace middleware, access logs, interface rate limiting, etc.).

  4. register_routers(app):

    • Mounts built-in system routes: common_router (public/authentication), system_router (users/roles/departments/menus), monitor_router (logs/online users/system monitoring), generator_router (code generation), task_router (scheduled tasks), ai_router, etc.

    • Triggers dynamic_router.init_app(app) to implement automatic discovery and mounting of plugin modules.

  5. register_static(app) and register_docs(app): Mounts local upload static directories and localizes/CDN-accelerates the JS/CSS static resources for Swagger UI / ReDoc.

  6. register_frontend(app): If a built dist directory exists, serves the single-page application statically under the /web path.

2.3.1 Backend Plugin Route Auto-Discovery and Registration Mechanism (DynamicRouter)

Stage: Occurs during the application factory creation phase at create_app() -> register_routers(app), executing route auto-discovery and registration immediately after synchronously registering built-in system routes, before the lifespan asynchronous lifecycle events.

2.3.1.1 Core Design Specifications and Conventions

The system follows the principle of Convention over Configuration, with the following requirements for the plugin directory structure:

2.3.1.2 Auto-Registration Implementation Principle

image.png

2.3.1.3 Key Source Code and Mechanism Analysis (app/core/discover.py)

  1. Singleton and result caching (DynamicRouterRegistry): Saves the root routing tree built from the first scan via self._cache, avoiding unnecessary disk I/O and reflection traversal during hot-reload or repeated calls.
  2. File tree pattern matching and sorting:
    base_package = importlib.import_module("app.plugin")  
    base_dir = Path(next(iter(base_package.__path__)))  
    controller_files = list(base_dir.glob("module_*/**/controller.py"))  
    controller_files.sort()
  1. Dynamic container route isolation (container_routers): Builds a separate APIRouter(prefix="/demo") for each top-level plugin (e.g., module_demo) as a container; all sub-controller routes within the plugin are mounted under this container, achieving natural route isolation and automatic prefix convergence between modules.

  2. Dynamic module loading and attribute reflection: Uses importlib.import_module to dynamically load modules, and filters all top-level exposed router instances via getattr and isinstance(attr_value, APIRouter).

  3. ID deduplication and exception circuit-breaker protection:

    • Maintains a seen_router_ids: set[int] collection, using Python object memory id(attr_value) to completely prevent duplicate route registration caused by multi-level imports or duplicate declarations;

    • Contains an internal _import_failure_hint diagnostic mechanism; when a plugin has syntax errors, missing dependencies, or lacks __init__.py, it prints detailed troubleshooting guidance and error stack traces, without causing the entire FastAPI service startup to crash, ensuring system robustness.

2.4 Lifespan Asynchronous Lifecycle Management

When Uvicorn completes basic binding, it triggers the lifespan asynchronous context manager:


3. Frontend Startup Process in Detail (Vue 3 + Vite)

The frontend project is located at frontend/web/, using Vue 3 Composition API + TypeScript.

image.png

3.1 Vite Build Environment and Configuration Loading

  1. Environment variable loading: According to the commands in package.json (e.g., pnpm dev corresponds to --mode development), Vite automatically reads .env and .env.development, injecting global variables such as VITE_PORT, VITE_PUBLIC_PATH.

  2. Plugin chain configuration (vite.config.ts):

    • Automatic on-demand component import (unplugin-vue-components): No need to manually declare imports for Element Plus components.

    • Automatic API import (unplugin-auto-import): Automatically injects commonly used APIs like ref, reactive, computed, useRouter.

    • SVG icon sprite loading and Mock/Proxy configuration (proxies /api/v1 requests to the backend development server).

3.2 Cascading Style Design and Entry Script (main.ts)

The entry file frontend/web/src/main.ts ensures deterministic style override order:

import "element-plus/theme-chalk/base.css"; // 1. Element Plus base styles  
import "@styles/tailwind.css";              // 2. Tailwind CSS atomic utility classes  
import "@styles/index.scss";                // 3. Project customization and global theme overrides (highest priority)

3.3 Strict Dependency Chain of the Plugin Registration System (initPlugins)

In frontend/web/src/plugins/index.ts, the plugin registration order has strict upstream-downstream dependency constraints:

  1. initStore(app): Initializes the Pinia state management store. Must be first, because subsequent route guards and directives need to read user state.
  2. initRouter(app): Configures Hash routing mode (createWebHashHistory, suitable for offline packaging and scenarios without server-side fallback), and installs before and after route guards.
  3. initGlobDirectives(app): Registers button-level permission directive v-auth, code highlighting, and other directives.
  4. initErrorHandle(app): Registers window.onerror and unhandledrejection global exception monitoring.
  5. initTerminal / initI18n / initCodeMirror: Registers terminal, internationalization language packs, and rich text/code editors.

3.4 Route Authentication Guards and Dynamic Route Mounting

After executing app.mount("#app"), the first screen navigation triggers route guards:

  1. Static base routes: The system pre-registers only whitelist routes such as /login, 404, 403, /redirect.

  2. Before-guard interception (setupBeforeEachGuard):

    • Starts the NProgress progress bar at the top of the page.

    • Not logged in: If accessing a protected route without a Token, intercepts and redirects to /login?redirect=....

    • Logged in: Checks whether the user Profile and permission routes have been generated. If not, initiates an API request to fetch the backend permission menu tree, parses it into Vue route records via MenuProcessor, dynamically injects them using router.addRoute, and uses next({ ...to, replace: true }) to re-trigger navigation to ensure dynamic routes take effect.


4. Frontend-Backend Interaction and Request Closed-Loop Flow Diagram

image.png

5. Summary Comparison

Phase Backend (FastAPI) Core Responsibilities Frontend (Vue 3 + Vite) Core Responsibilities
Configuration Loading Reads .env configuration for the specified environment, initializes Pydantic BaseSettings singleton Parses Vite environment variables, injects import.meta.env
Base Instantiation Creates FastAPI instance, assembles middleware chain and various business module routes Creates Vue application instance, imports cascading stylesheets (Element Plus -> Tailwind -> SCSS)
Plugin/Dependency Startup Lifespan triggers: executes database check and migration, establishes Redis connection pool, preheats dictionary parameters, starts scheduled task scheduler initPlugins assembles in order: Pinia store -> Vue Router guards -> global permission directives -> internationalization and functional components
Runtime Readiness Starts HTTP / WebSocket listening, console outputs readiness panel Mounts #app, triggers route guard authentication, asynchronously fetches dynamic menu tree to complete full system rendering