fastapi-setup
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFastAPI Project Setup
FastAPI项目搭建
Scaffold a complete, production-ready FastAPI project. Ask the user for the project name if not provided (e.g. "myapi"). Use it throughout as .
{project}搭建完整的生产级可用FastAPI项目。如果用户未提供项目名称请向用户询问(例如 "myapi"),后续所有位置统一使用占位符替换。
{project}Project Structure
项目结构
Create all files exactly as shown. Replace with the actual project name.
{project}{project}/
├── src/{project}/
│ ├── agents/
│ │ └── __init__.py
│ ├── api/
│ │ ├── routes/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ └── core.py
│ │ ├── __init__.py
│ │ └── lifespan.py
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── database.py
│ │ └── models.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
├── justfile
├── Dockerfile
├── docker-compose.yml
├── CLAUDE.md
├── .vscode/
│ └── settings.json
├── .env.example
└── .python-version严格按照下方结构创建所有文件,将替换为实际项目名称。
{project}{project}/
├── src/{project}/
│ ├── agents/
│ │ └── __init__.py
│ ├── api/
│ │ ├── routes/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ └── core.py
│ │ ├── __init__.py
│ │ └── lifespan.py
│ ├── config/
│ │ ├── __init__.py
│ │ └── settings.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── database.py
│ │ └── models.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
├── justfile
├── Dockerfile
├── docker-compose.yml
├── CLAUDE.md
├── .vscode/
│ └── settings.json
├── .env.example
└── .python-versionFile Templates
文件模板
pyproject.toml
pyproject.tomlpyproject.toml
pyproject.tomltoml
[project]
name = "{project}"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.13"
dependencies = [
"bcrypt>=4.0",
"email-validator>=2.3.0",
"fastapi>=0.115.0",
"psycopg2-binary>=2.9.11",
"pydantic>=2.10.0",
"pydantic-settings>=2.7.0",
"python-jose[cryptography]>=3.5.0",
"sqlalchemy>=2.0.0",
"uvicorn>=0.30.0",
]
[project.scripts]
start = "{project}.main:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/{project}"]
only-include = ["src"]toml
[project]
name = "{project}"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.13"
dependencies = [
"bcrypt>=4.0",
"email-validator>=2.3.0",
"fastapi>=0.115.0",
"psycopg2-binary>=2.9.11",
"pydantic>=2.10.0",
"pydantic-settings>=2.7.0",
"python-jose[cryptography]>=3.5.0",
"sqlalchemy>=2.0.0",
"uvicorn>=0.30.0",
]
[project.scripts]
start = "{project}.main:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/{project}"]
only-include = ["src"].env.example
.env.example.env.example
.env.exampleSECRET_KEY=changeme-use-a-long-random-string
PG_DATABASE_URL=postgresql://user:password@localhost:5432/{project}
SQLITE_DATABASE_URL={project}.dbSECRET_KEY=changeme-use-a-long-random-string
PG_DATABASE_URL=postgresql://user:password@localhost:5432/{project}
SQLITE_DATABASE_URL={project}.db.python-version
.python-version.python-version
.python-version3.133.13Dockerfile
DockerfileDockerfile
Dockerfiledockerfile
undefineddockerfile
undefinedBuild: docker build -t {project} .
Build: docker build -t {project} .
FROM python:3.13-slim
WORKDIR /app
RUN apt-get update && apt-get install -y
gcc
curl
&& rm -rf /var/lib/apt/lists/*
gcc
curl
&& rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
COPY pyproject.toml uv.lock ./
COPY src/ ./src/
RUN uv sync --frozen --no-dev --no-cache
EXPOSE 8000
ENV PYTHONUNBUFFERED=1
CMD [".venv/bin/start"]
undefinedFROM python:3.13-slim
WORKDIR /app
RUN apt-get update && apt-get install -y
gcc
curl
&& rm -rf /var/lib/apt/lists/*
gcc
curl
&& rm -rf /var/lib/apt/lists/*
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
COPY pyproject.toml uv.lock ./
COPY src/ ./src/
RUN uv sync --frozen --no-dev --no-cache
EXPOSE 8000
ENV PYTHONUNBUFFERED=1
CMD [".venv/bin/start"]
undefineddocker-compose.yml
docker-compose.ymldocker-compose.yml
docker-compose.ymlyaml
services:
api:
build:
context: .
dockerfile: Dockerfile
image: {project}:latest
container_name: {project}
ports:
- "8000:8000"
restart: unless-stopped
environment:
- SECRET_KEY=${SECRET_KEY}
- PG_DATABASE_URL=${PG_DATABASE_URL}
- SQLITE_DATABASE_URL=${SQLITE_DATABASE_URL}yaml
services:
api:
build:
context: .
dockerfile: Dockerfile
image: {project}:latest
container_name: {project}
ports:
- "8000:8000"
restart: unless-stopped
environment:
- SECRET_KEY=${SECRET_KEY}
- PG_DATABASE_URL=${PG_DATABASE_URL}
- SQLITE_DATABASE_URL=${SQLITE_DATABASE_URL}CLAUDE.md
CLAUDE.mdCLAUDE.md
CLAUDE.mdmarkdown
undefinedmarkdown
undefinedCLAUDE.md
CLAUDE.md
Architecture and development rules for this FastAPI project.
Architecture and development rules for this FastAPI project.
Project Structure
Project Structure
```
src/{project}/
├── agents/ # AI agents (LLM reasoning units)
├── api/
│ ├── routes/ # Thin HTTP handlers only
│ ├── lifespan.py # Startup/shutdown
│ └── init.py
├── config/ # Settings (pydantic-settings)
├── db/ # SQLAlchemy engine, session, ORM models
├── models/ # Pydantic models (shared across routes, agents, services)
├── services/ # Business logic
│ ├── init.py # Dependency functions (get_*_service)
│ └── auth.py # One file per domain
└── utils/ # Stateless helpers (hashing, JWT, etc.)
```
```
src/{project}/
├── agents/ # AI agents (LLM reasoning units)
├── api/
│ ├── routes/ # Thin HTTP handlers only
│ ├── lifespan.py # Startup/shutdown
│ └── init.py
├── config/ # Settings (pydantic-settings)
├── db/ # SQLAlchemy engine, session, ORM models
├── models/ # Pydantic models (shared across routes, agents, services)
├── services/ # Business logic
│ ├── init.py # Dependency functions (get_*_service)
│ └── auth.py # One file per domain
└── utils/ # Stateless helpers (hashing, JWT, etc.)
```
Layer Rules
Layer Rules
Routes (api/routes/
)
api/routes/Routes (api/routes/
)
api/routes/- Thin wrappers only — no business logic, no db queries
- Only call services via
Depends() - Import services from not from other routes
services
- Thin wrappers only — no business logic, no db queries
- Only call services via
Depends() - Import services from not from other routes
services
Services (services/
)
services/Services (services/
)
services/- All business logic lives here
- Receive in
db: Session, never import__init__directlyget_db - Can be used by routes AND agents
- Dependency functions () go in
get_*_service— not in route filesservices/__init__.py
- All business logic lives here
- Receive in
db: Session, never import__init__directlyget_db - Can be used by routes AND agents
- Dependency functions () go in
get_*_service— not in route filesservices/__init__.py
Models (models/
)
models/Models (models/
)
models/- All Pydantic models go here — never inside
api/ - Shared freely across routes, services, and agents
- All Pydantic models go here — never inside
api/ - Shared freely across routes, services, and agents
Agents (agents/
)
agents/Agents (agents/
)
agents/- AI reasoning units — call LLMs, use tools
- Receive services via constructor, never import or
dbdirectlyget_db - Do not contain business logic — delegate to services
- AI reasoning units — call LLMs, use tools
- Receive services via constructor, never import or
dbdirectlyget_db - Do not contain business logic — delegate to services
DB (db/
)
db/DB (db/
)
db/- — engine, session,
database.pyget_db - — SQLAlchemy ORM models using
models.py/Mappedmapped_column - Only imported in and
services/utils/
- — engine, session,
database.pyget_db - — SQLAlchemy ORM models using
models.py/Mappedmapped_column - Only imported in and
services/utils/
Violations to Flag
Violations to Flag
- imported inside any
from {project}.dbfileapi/routes/ - used directly in a route handler
get_db - Pydantic models defined inside
api/ - functions defined inside route files
get_*_service - Business logic (db queries, data conditionals) inside route handlers
- Agents importing or
dbdirectlySession
- imported inside any
from {project}.dbfileapi/routes/ - used directly in a route handler
get_db - Pydantic models defined inside
api/ - functions defined inside route files
get_*_service - Business logic (db queries, data conditionals) inside route handlers
- Agents importing or
dbdirectlySession
Development Commands
Development Commands
```bash
just dev # start with hot reload
just start # start production mode
just lint # ruff check
just lint-fix # ruff check --fix
just format # ruff format
just typecheck # pyright
just docker-build # build image
just docker-up # start container
just docker-down # stop container
just docker-logs # tail logs
```
```bash
just dev # start with hot reload
just start # start production mode
just lint # ruff check
just lint-fix # ruff check --fix
just format # ruff format
just typecheck # pyright
just docker-build # build image
just docker-up # start container
just docker-down # stop container
just docker-logs # tail logs
```
Adding a New Feature
Adding a New Feature
- Model →
models/{domain}.py - ORM model →
db/models.py - Service →
services/{domain}.py - Dependency function →
services/__init__.py - Route → (thin wrapper)
api/routes/{domain}.py - Register router → +
api/routes/__init__.pymain.py - Agent (if AI needed) → , inject service via constructor
agents/{domain}.py
undefined- Model →
models/{domain}.py - ORM model →
db/models.py - Service →
services/{domain}.py - Dependency function →
services/__init__.py - Route → (thin wrapper)
api/routes/{domain}.py - Register router → +
api/routes/__init__.pymain.py - Agent (if AI needed) → , inject service via constructor
agents/{domain}.py
undefined.vscode/settings.json
.vscode/settings.json.vscode/settings.json
.vscode/settings.jsonjson
{
"files.exclude": {
"**/__pycache__": true,
"**/*.ruff_cache": true,
"**/*.pyc": true
}
}json
{
"files.exclude": {
"**/__pycache__": true,
"**/*.ruff_cache": true,
"**/*.pyc": true
}
}justfile
justfilejustfile
justfilejust
undefinedjust
undefinedApplication Commands
Application Commands
start:
uv run start
dev:
API_RELOAD=true uv run start
start:
uv run start
dev:
API_RELOAD=true uv run start
Development Commands
Development Commands
install:
uv sync
add package:
uv add {{package}}
add-dev package:
uv add --dev {{package}}
update:
uv lock --upgrade
uv sync
install:
uv sync
add package:
uv add {{package}}
add-dev package:
uv add --dev {{package}}
update:
uv lock --upgrade
uv sync
Docker
Docker
docker-build:
docker build -t {project}:latest .
docker-up:
docker compose up -d
docker-down:
docker compose down
docker-logs:
docker compose logs -f
docker-build:
docker build -t {project}:latest .
docker-up:
docker compose up -d
docker-down:
docker compose down
docker-logs:
docker compose logs -f
Code Quality
Code Quality
format:
uv run ruff format .
lint path=".":
uv run ruff check {{path}}
lint-fix path=".":
uv run ruff check --fix {{path}}
typecheck path=".":
uv run pyright {{path}}
undefinedformat:
uv run ruff format .
lint path=".":
uv run ruff check {{path}}
lint-fix path=".":
uv run ruff check --fix {{path}}
typecheck path=".":
uv run pyright {{path}}
undefinedsrc/{project}/__init__.py
src/{project}/__init__.pysrc/{project}/__init__.py
src/{project}/__init__.pypython
undefinedpython
undefinedsrc/{project}/main.py
src/{project}/main.pysrc/{project}/main.py
src/{project}/main.pypython
import os
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from {project}.api.lifespan import lifespan
from {project}.api.routes import auth_router, core_router
app = FastAPI(lifespan=lifespan)
origins = [
"http://localhost:5173",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
expose_headers=["Content-Disposition"],
)
app.include_router(core_router)
app.include_router(auth_router)
def main():
reload = os.getenv("API_RELOAD", "false").lower() == "true"
if reload:
uvicorn.run("{project}.main:app", host="0.0.0.0", port=8000, reload=True)
else:
uvicorn.run(app, host="0.0.0.0", port=8000)
if __name__ == "__main__":
main()python
import os
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from {project}.api.lifespan import lifespan
from {project}.api.routes import auth_router, core_router
app = FastAPI(lifespan=lifespan)
origins = [
"http://localhost:5173",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization"],
expose_headers=["Content-Disposition"],
)
app.include_router(core_router)
app.include_router(auth_router)
def main():
reload = os.getenv("API_RELOAD", "false").lower() == "true"
if reload:
uvicorn.run("{project}.main:app", host="0.0.0.0", port=8000, reload=True)
else:
uvicorn.run(app, host="0.0.0.0", port=8000)
if __name__ == "__main__":
main()src/{project}/config/__init__.py
src/{project}/config/__init__.pysrc/{project}/config/__init__.py
src/{project}/config/__init__.pypython
undefinedpython
undefinedsrc/{project}/config/settings.py
src/{project}/config/settings.pysrc/{project}/config/settings.py
src/{project}/config/settings.pypython
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# Secret key for signing JWT tokens
secret_key: str
# SQLite database file path (fallback)
sqlite_database_url: str = "{project}.db"
# Full PostgreSQL connection string
pg_database_url: str | None = None
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""Return a cached Settings instance."""
return Settings()
settings = get_settings()python
from functools import lru_cache
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
# Secret key for signing JWT tokens
secret_key: str
# SQLite database file path (fallback)
sqlite_database_url: str = "{project}.db"
# Full PostgreSQL connection string
pg_database_url: str | None = None
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""Return a cached Settings instance."""
return Settings()
settings = get_settings()src/{project}/db/__init__.py
src/{project}/db/__init__.pysrc/{project}/db/__init__.py
src/{project}/db/__init__.pypython
undefinedpython
undefinedsrc/{project}/db/database.py
src/{project}/db/database.pysrc/{project}/db/database.py
src/{project}/db/database.pypython
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from {project}.config.settings import settings
def create_sql_light_engine(path: str | None = None):
if path is None:
path = settings.sqlite_database_url
if not path:
raise ValueError("SQLITE_DATABASE_URL environment variable is not set.")
return create_engine(f"sqlite:///{path}")
def create_postgres_engine(connection_string: str | None = None):
if connection_string is None:
connection_string = settings.pg_database_url
if not connection_string:
raise ValueError("PG_DATABASE_URL environment variable is not set.")
return create_engine(connection_string)
Base = declarative_base()
engine = create_sql_light_engine()
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = Session()
try:
yield db
finally:
db.close()python
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from {project}.config.settings import settings
def create_sql_light_engine(path: str | None = None):
if path is None:
path = settings.sqlite_database_url
if not path:
raise ValueError("SQLITE_DATABASE_URL environment variable is not set.")
return create_engine(f"sqlite:///{path}")
def create_postgres_engine(connection_string: str | None = None):
if connection_string is None:
connection_string = settings.pg_database_url
if not connection_string:
raise ValueError("PG_DATABASE_URL environment variable is not set.")
return create_engine(connection_string)
Base = declarative_base()
engine = create_sql_light_engine()
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = Session()
try:
yield db
finally:
db.close()src/{project}/db/models.py
src/{project}/db/models.pysrc/{project}/db/models.py
src/{project}/db/models.pypython
from datetime import datetime
from sqlalchemy import func
from sqlalchemy.orm import Mapped, mapped_column
from {project}.db.database import Base
class UserRecord(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
email: Mapped[str] = mapped_column(unique=True, nullable=False)
name: Mapped[str | None] = mapped_column(nullable=True)
phone: Mapped[str | None] = mapped_column(nullable=True)
avatar_url: Mapped[str | None] = mapped_column(nullable=True)
password_hash: Mapped[str | None] = mapped_column(nullable=True)
created_at: Mapped[datetime] = mapped_column(default=func.now())python
from datetime import datetime
from sqlalchemy import func
from sqlalchemy.orm import Mapped, mapped_column
from {project}.db.database import Base
class UserRecord(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
email: Mapped[str] = mapped_column(unique=True, nullable=False)
name: Mapped[str | None] = mapped_column(nullable=True)
phone: Mapped[str | None] = mapped_column(nullable=True)
avatar_url: Mapped[str | None] = mapped_column(nullable=True)
password_hash: Mapped[str | None] = mapped_column(nullable=True)
created_at: Mapped[datetime] = mapped_column(default=func.now())src/{project}/utils/__init__.py
src/{project}/utils/__init__.pysrc/{project}/utils/__init__.py
src/{project}/utils/__init__.pypython
undefinedpython
undefinedsrc/{project}/utils/auth.py
src/{project}/utils/auth.pysrc/{project}/utils/auth.py
src/{project}/utils/auth.pypython
from datetime import datetime, timedelta, timezone
import bcrypt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from {project}.config.settings import settings
from {project}.db.database import get_db
from {project}.db.models import UserRecord
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 hours
security = HTTPBearer(auto_error=False)
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(plain: str, hashed: str) -> bool:
return bcrypt.checkpw(plain.encode(), hashed.encode())
def create_token(user_id: int) -> str:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode({"sub": str(user_id), "exp": expire}, settings.secret_key, algorithm=ALGORITHM)
def get_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(security),
db: Session = Depends(get_db),
) -> UserRecord:
if not credentials or credentials.scheme != "Bearer":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing or invalid authorization",
)
try:
payload = jwt.decode(credentials.credentials, settings.secret_key, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user_id = int(user_id)
except (JWTError, ValueError):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.query(UserRecord).filter(UserRecord.id == user_id).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return userpython
from datetime import datetime, timedelta, timezone
import bcrypt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from {project}.config.settings import settings
from {project}.db.database import get_db
from {project}.db.models import UserRecord
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 hours
security = HTTPBearer(auto_error=False)
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(plain: str, hashed: str) -> bool:
return bcrypt.checkpw(plain.encode(), hashed.encode())
def create_token(user_id: int) -> str:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
return jwt.encode({"sub": str(user_id), "exp": expire}, settings.secret_key, algorithm=ALGORITHM)
def get_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(security),
db: Session = Depends(get_db),
) -> UserRecord:
if not credentials or credentials.scheme != "Bearer":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing or invalid authorization",
)
try:
payload = jwt.decode(credentials.credentials, settings.secret_key, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user_id = int(user_id)
except (JWTError, ValueError):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.query(UserRecord).filter(UserRecord.id == user_id).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return usersrc/{project}/api/__init__.py
src/{project}/api/__init__.pysrc/{project}/api/__init__.py
src/{project}/api/__init__.pypython
undefinedpython
undefinedsrc/{project}/api/lifespan.py
src/{project}/api/lifespan.pysrc/{project}/api/lifespan.py
src/{project}/api/lifespan.pypython
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy import text
from {project}.db.database import Base, engine
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan handler for startup and shutdown events."""
print("\n" + "=" * 60)
print(f"Starting {project} API")
print("=" * 60)
Base.metadata.create_all(bind=engine)
print("Database tables created/verified")
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
print("Database connection successful")
except Exception as e:
print(f"Database connection failed: {e}")
print("Application may not function correctly!")
yield
engine.dispose()
print("{project} API shutdown complete")python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy import text
from {project}.db.database import Base, engine
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan handler for startup and shutdown events."""
print("\n" + "=" * 60)
print(f"Starting {project} API")
print("=" * 60)
Base.metadata.create_all(bind=engine)
print("Database tables created/verified")
try:
with engine.connect() as conn:
conn.execute(text("SELECT 1"))
print("Database connection successful")
except Exception as e:
print(f"Database connection failed: {e}")
print("Application may not function correctly!")
yield
engine.dispose()
print("{project} API shutdown complete")src/{project}/agents/__init__.py
src/{project}/agents/__init__.pysrc/{project}/agents/__init__.py
src/{project}/agents/__init__.pypython
undefinedpython
undefinedsrc/{project}/models/__init__.py
src/{project}/models/__init__.pysrc/{project}/models/__init__.py
src/{project}/models/__init__.pypython
from .auth import LoginRequest, SignupRequest, TokenResponse, UserResponse
__all__ = ["LoginRequest", "SignupRequest", "TokenResponse", "UserResponse"]python
from .auth import LoginRequest, SignupRequest, TokenResponse, UserResponse
__all__ = ["LoginRequest", "SignupRequest", "TokenResponse", "UserResponse"]src/{project}/models/auth.py
src/{project}/models/auth.pysrc/{project}/models/auth.py
src/{project}/models/auth.pypython
from datetime import datetime
from pydantic import BaseModel, EmailStr
class LoginRequest(BaseModel):
email: EmailStr
password: str
class SignupRequest(BaseModel):
email: EmailStr
password: str
name: str | None = None
class UserResponse(BaseModel):
id: int
email: str
name: str | None
created_at: datetime
model_config = {"from_attributes": True}
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserResponsepython
from datetime import datetime
from pydantic import BaseModel, EmailStr
class LoginRequest(BaseModel):
email: EmailStr
password: str
class SignupRequest(BaseModel):
email: EmailStr
password: str
name: str | None = None
class UserResponse(BaseModel):
id: int
email: str
name: str | None
created_at: datetime
model_config = {"from_attributes": True}
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
user: UserResponsesrc/{project}/api/routes/__init__.py
src/{project}/api/routes/__init__.pysrc/{project}/api/routes/__init__.py
src/{project}/api/routes/__init__.pypython
from .auth import router as auth_router
from .core import router as core_router
__all__ = ["auth_router", "core_router"]python
from .auth import router as auth_router
from .core import router as core_router
__all__ = ["auth_router", "core_router"]src/{project}/api/routes/core.py
src/{project}/api/routes/core.pysrc/{project}/api/routes/core.py
src/{project}/api/routes/core.pypython
from fastapi import APIRouter
router = APIRouter(tags=["core"])
@router.get("/")
def root():
return {"ok": True, "message": "{project} API is running", "version": "0.1.0"}
@router.get("/healthz")
def healthz():
return {"ok": True, "service": "{project}-api", "version": "0.1.0"}python
from fastapi import APIRouter
router = APIRouter(tags=["core"])
@router.get("/")
def root():
return {"ok": True, "message": "{project} API is running", "version": "0.1.0"}
@router.get("/healthz")
def healthz():
return {"ok": True, "service": "{project}-api", "version": "0.1.0"}src/{project}/api/routes/auth.py
src/{project}/api/routes/auth.pysrc/{project}/api/routes/auth.py
src/{project}/api/routes/auth.pypython
from fastapi import APIRouter, Depends, status
from {project}.models import LoginRequest, SignupRequest, TokenResponse
from {project}.services import get_auth_service
from {project}.services.auth import AuthService
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/signup", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
def signup(body: SignupRequest, service: AuthService = Depends(get_auth_service)):
return service.signup(body)
@router.post("/login", response_model=TokenResponse)
def login(body: LoginRequest, service: AuthService = Depends(get_auth_service)):
return service.login(body)python
from fastapi import APIRouter, Depends, status
from {project}.models import LoginRequest, SignupRequest, TokenResponse
from {project}.services import get_auth_service
from {project}.services.auth import AuthService
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/signup", response_model=TokenResponse, status_code=status.HTTP_201_CREATED)
def signup(body: SignupRequest, service: AuthService = Depends(get_auth_service)):
return service.signup(body)
@router.post("/login", response_model=TokenResponse)
def login(body: LoginRequest, service: AuthService = Depends(get_auth_service)):
return service.login(body)src/{project}/services/__init__.py
src/{project}/services/__init__.pysrc/{project}/services/__init__.py
src/{project}/services/__init__.pypython
from fastapi import Depends
from sqlalchemy.orm import Session
from {project}.db.database import get_db
from {project}.services.auth import AuthService
def get_auth_service(db: Session = Depends(get_db)) -> AuthService:
return AuthService(db)python
from fastapi import Depends
from sqlalchemy.orm import Session
from {project}.db.database import get_db
from {project}.services.auth import AuthService
def get_auth_service(db: Session = Depends(get_db)) -> AuthService:
return AuthService(db)src/{project}/services/auth.py
src/{project}/services/auth.pysrc/{project}/services/auth.py
src/{project}/services/auth.pypython
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from {project}.db.models import UserRecord
from {project}.models import LoginRequest, SignupRequest, TokenResponse, UserResponse
from {project}.utils.auth import create_token, hash_password, verify_password
class AuthService:
def __init__(self, db: Session):
self.db = db
def signup(self, body: SignupRequest) -> TokenResponse:
if self.db.query(UserRecord).filter(UserRecord.email == body.email).first():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Email already registered"
)
user = UserRecord(
email=body.email,
name=body.name,
password_hash=hash_password(body.password),
)
self.db.add(user)
self.db.commit()
self.db.refresh(user)
return TokenResponse(
access_token=create_token(user.id),
user=UserResponse.model_validate(user),
)
def login(self, body: LoginRequest) -> TokenResponse:
user = self.db.query(UserRecord).filter(UserRecord.email == body.email).first()
if (
not user
or not user.password_hash
or not verify_password(body.password, user.password_hash)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid email or password",
)
return TokenResponse(
access_token=create_token(user.id),
user=UserResponse.model_validate(user),
)python
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from {project}.db.models import UserRecord
from {project}.models import LoginRequest, SignupRequest, TokenResponse, UserResponse
from {project}.utils.auth import create_token, hash_password, verify_password
class AuthService:
def __init__(self, db: Session):
self.db = db
def signup(self, body: SignupRequest) -> TokenResponse:
if self.db.query(UserRecord).filter(UserRecord.email == body.email).first():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Email already registered"
)
user = UserRecord(
email=body.email,
name=body.name,
password_hash=hash_password(body.password),
)
self.db.add(user)
self.db.commit()
self.db.refresh(user)
return TokenResponse(
access_token=create_token(user.id),
user=UserResponse.model_validate(user),
)
def login(self, body: LoginRequest) -> TokenResponse:
user = self.db.query(UserRecord).filter(UserRecord.email == body.email).first()
if (
not user
or not user.password_hash
or not verify_password(body.password, user.password_hash)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid email or password",
)
return TokenResponse(
access_token=create_token(user.id),
user=UserResponse.model_validate(user),
)Empty __init__.py
files
__init__.py空__init__.py
文件
__init__.pyCreate empty for all packages that don't have content listed above.
__init__.py为所有未在上方提供内容的包目录创建空的文件。
__init__.pyAfter Scaffolding — Tell the User
脚手架生成完成后告知用户
Once all files are created, tell the user:
bash
undefined所有文件创建完成后,告知用户以下内容:
bash
undefinedInstall dependencies with uv
使用uv安装依赖
uv sync
uv sync
Copy env file and fill in your values
复制环境变量文件并填写配置值
cp .env.example .env
cp .env.example .env
Start the dev server
启动开发服务器
uv run start
The API will be at `http://localhost:8000`. Endpoints:
- `GET /healthz` — health check (no auth)
- `POST /auth/signup` — create account, returns JWT
- `POST /auth/login` — login, returns JWT
- `GET /` — protected root (requires `Authorization: Bearer <token>`)
To protect any route, add `user: UserRecord = Depends(get_current_user)` as a parameter.
---uv run start
接口将运行在 `http://localhost:8000`,可用端点如下:
- `GET /healthz` — 健康检查(无需认证)
- `POST /auth/signup` — 创建账号,返回JWT
- `POST /auth/login` — 登录,返回JWT
- `GET /` — 受保护的根路由(需要携带`Authorization: Bearer <token>`请求头)
如需保护任意路由,只需将`user: UserRecord = Depends(get_current_user)`作为参数添加到路由处理函数即可。
---Important Notes
重要说明
- PostgreSQL is required by default. The in
PG_DATABASE_URLmust be set before running..env - Tables are auto-created on startup via — no migrations needed to start.
Base.metadata.create_all() - To add new models: extend , then add routes/services as needed.
src/{project}/db/models.py - To add new routes: create , export the router in
src/{project}/api/routes/mything.py, and include it inroutes/__init__.py.main.py
- 默认需要PostgreSQL,运行前必须填写中的
.env配置PG_DATABASE_URL - 启动时会通过自动创建表,初始开发无需配置迁移
Base.metadata.create_all() - 新增模型:扩展,然后按需添加路由和服务即可
src/{project}/db/models.py - 新增路由:创建,在
src/{project}/api/routes/mything.py中导出路由,然后在routes/__init__.py中注册路由即可main.py