Skip to content

Configuration

The backend reads .env from the project root via Pydantic Settings (app/core/config.py). The frontend reads web/.env via Vite.

Backend .env

dotenv
# ---- App ----
APP_TITLE=FastSoyAdmin
APP_DEBUG=true
SECRET_KEY=015a42020f023ac2c3eda3d45fe5ca3fef8921ce63589f6d4fcdef9814cd7fa7

# ---- CORS ----
CORS_ORIGINS=["http://localhost:9527"]

# ---- DB ----
DB_URL=postgres://postgres:password@localhost:5432/fastsoyadmin

# ---- Redis ----
REDIS_URL=redis://localhost:6379/0

# ---- JWT ----
JWT_ALGORITHM=HS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=720         # 12 hours
JWT_REFRESH_TOKEN_EXPIRE_MINUTES=10080      # 7 days

# ---- Monitoring ----
RADAR_ENABLED=true

# ---- Rate limit (fastapi-guard, https://fastapi-guard.com/) ----
GUARD_ENABLED=true
GUARD_RATE_LIMIT=100
GUARD_RATE_LIMIT_WINDOW=60
GUARD_AUTO_BAN_THRESHOLD=10
GUARD_AUTO_BAN_DURATION=21600

# ---- Reverse-proxy header reconciliation ----
PROXY_HEADERS_ENABLED=false
TRUSTED_HOSTS=["127.0.0.1"]

# ---- Logging ----
LOG_INFO_RETENTION=30 days
dotenv
APP_DEBUG=false
SECRET_KEY=<generate a fresh 256-bit random>

CORS_ORIGINS=["https://your-admin.example.com"]

DB_URL=postgres://user:pwd@db:5432/fastsoyadmin?maxsize=50&minsize=5

REDIS_URL=redis://:strong-password@redis:6379/0

# Required behind Nginx / a gateway
PROXY_HEADERS_ENABLED=true
TRUSTED_HOSTS=["10.0.0.0/8"]

All settings

SettingDefaultPurpose
VERSION0.1.0application version (affects OpenAPI)
APP_TITLEFastSoyAdminOpenAPI title
APP_DESCRIPTIONDescriptionOpenAPI description
APP_DEBUGfalseenables /openapi.json + Swagger UI
SECRET_KEYdev built-inJWT signing key + Sqids alphabet seed (must change in prod)
CORS_ORIGINS["*"]allowed origins
CORS_ALLOW_CREDENTIALStrue
CORS_ALLOW_METHODS["*"]
CORS_ALLOW_HEADERS["*"]
DB_URLpostgres://postgres:password@localhost:5432/fastsoyadminTortoise URL; see Switching DB for SQLite/MySQL/MSSQL
TORTOISE_ORMauto-builtdon't set manually; multi-line JSON in .env is fragile
REDIS_URLredis://redis:6379/0Redis URL
JWT_ALGORITHMHS256
JWT_ACCESS_TOKEN_EXPIRE_MINUTES720access token lifetime (minutes)
JWT_REFRESH_TOKEN_EXPIRE_MINUTES10080refresh token lifetime (minutes)
DATETIME_FORMAT"%Y-%m-%d %H:%M:%S"format for to_dict's fmtCreatedAt etc.
RADAR_ENABLEDtrueenable in-house Radar monitoring (request/SQL/exception dashboard; implemented with reference to fastapi-radar)
GUARD_ENABLEDtrueenable fastapi-guard rate limiting
GUARD_RATE_LIMIT100requests allowed per window
GUARD_RATE_LIMIT_WINDOW60window size (seconds)
GUARD_AUTO_BAN_THRESHOLD10violations before auto-ban
GUARD_AUTO_BAN_DURATION21600auto-ban duration (seconds; default 6h)
PROXY_HEADERS_ENABLEDfalsereconcile X-Forwarded-* to true client IP
TRUSTED_HOSTS["127.0.0.1"]trusted upstream list (IP / CIDR)
LOG_INFO_RETENTION30 dayslog retention; supports seconds/minutes/hours/days/weeks/months/years
PROJECT_ROOTparent of app/auto-derived
BASE_DIRPROJECT_ROOT.parentauto-derived
LOGS_ROOTBASE_DIR / "logs/"mkdir at startup
STATIC_ROOTBASE_DIR / "static/"mkdir at startup

Module-local config

A business module can declare its own Settings in app/business/<name>/config.py:

python
# app/business/hr/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict

class HRSettings(BaseSettings):
    HR_TAG_PER_EMPLOYEE_LIMIT: int = 5
    DB_URL: str | None = None              # if different from main, autodiscover registers a separate connection

    model_config = SettingsConfigDict(env_file=".env", extra="ignore", env_prefix="HR_")

BIZ_SETTINGS = HRSettings()

env_prefix carves out a namespace: in .env use HR_TAG_PER_EMPLOYEE_LIMIT=8, HR_DB_URL=postgres://....

Business code:

python
from app.business.hr.config import BIZ_SETTINGS
limit = BIZ_SETTINGS.HR_TAG_PER_EMPLOYEE_LIMIT

For standalone DB behavior see Autodiscover / standalone DB.

Frontend .env

dotenv
# web/.env

VITE_AUTH_ROUTE_MODE=dynamic
VITE_ROUTE_HOME=home

VITE_SERVICE_SUCCESS_CODE=0000
VITE_SERVICE_LOGOUT_CODES=2100,2101,2104,2105
VITE_SERVICE_MODAL_LOGOUT_CODES=2102,2106
VITE_SERVICE_EXPIRED_TOKEN_CODES=2103

VITE_SERVICE_BASE_URL=/api/v1
VITE_OTHER_SERVICE_BASE_URL={"demo": "/demo"}

VITE_ICON_PREFIX=icon
VITE_ICON_LOCAL_PREFIX=icon-local

See Frontend / Request / intro.

Code style

  • ruff.toml: line 200, double-quote, sorted imports, rules E/F/I
  • basedpyright: standard mode, target app/ (config in pyproject.toml [tool.basedpyright])
  • Frontend ESLint: @soybeanjs/eslint-config-vue

See Commands and Backend style.

Rotating SECRET_KEY

SECRET_KEY is used for two things:

  1. JWT HMAC signing → rotation invalidates all existing JWTs (everyone re-logs in)
  2. Sqids alphabet seed → rotation invalidates all historical sqids (external links / bookmarks / integrations break)

If you must rotate in production:

  • announce + give external integrations time to migrate
  • bump an API version
  • keep the old SECRET_KEY around for "dual signing" temporarily (custom code required)

See Sqids / rotating SECRET_KEY.

See also

基于 MIT 协议发布