๐ Architect view
Coupling, patterns, Zones of Pain, refactoring priorities ยท Software Architect, Principal Engineer
โก Agent trace ยท 13 steps ยท $0.0369 ยท click to expand
search_claims(query='architecture layered event-driven monolithic', category='structural') โ
search_claims(query='high coupling dependency module', category='structural') โ
search_claims(query='design pattern factory singleton observer', category='structural') โ
search_claims(category='structural', query='FastAPI router endpoint API route') โ
search_claims(category='structural', query='model database table SQLAlchemy') โ
search_claims(category='structural', query='React component hook context provider') โ
get_dependencies(file_path='backend/app/api/main.py') โ
get_dependencies(file_path='backend/app/main.py') โ
get_dependencies(file_path='backend/app/api/deps.py') โ
get_dependencies(file_path='backend/app/models.py') โ
get_dependencies(file_path='backend/app/api/routes/users.py') โ
get_dependencies(file_path='backend/app/api/routes/items.py') โ
get_dependencies(file_path='backend/app/api/routes/login.py') โ
get_dependencies(file_path='backend/app/api/routes/private.py') โ
get_dependencies(file_path='backend/app/api/routes/utils.py') โ
get_dependencies(file_path='frontend/src/hooks/useAuth.ts') โ
get_dependencies(file_path='frontend/src/client/sdk.gen.ts') โ
get_dependencies(file_path='frontend/src/routes/_layout.tsx') โ
get_dependencies(file_path='frontend/src/components/ui/sidebar.tsx') โ
get_file_claims(file_path='frontend/src/hooks/useAuth.ts') โ
get_file_claims(file_path='backend/app/utils.py') โ
get_dependencies(file_path='frontend/src/client/index.ts') โ
get_dependencies(file_path='frontend/src/routeTree.gen.ts') โ
read_source_file(file_path='frontend/src/hooks/useAuth.ts', start_line=1) โ
get_dependencies(file_path='frontend/src/utils.ts') โ
get_dependencies(file_path='frontend/src/main.tsx') โ
read_source_file(end_line=60, file_path='backend/app/main.py') โ
read_source_file(end_line=60, file_path='backend/app/api/main.py') โ
read_source_file(end_line=60, file_path='backend/app/api/deps.py') โ
read_source_file(end_line=70, file_path='backend/app/models.py') โ
get_file_claims(file_path='backend/app/core/config.py') โ
get_file_claims(file_path='backend/app/core/security.py') โ
read_source_file(end_line=80, file_path='backend/app/api/routes/users.py') โ
read_source_file(end_line=80, file_path='backend/app/api/routes/items.py') โ
get_file_claims(file_path='backend/app/crud.py') โ
get_dependencies(file_path='backend/app/crud.py') โ
read_source_file(end_line=60, file_path='backend/app/crud.py') โ
get_dependencies(file_path='frontend/src/components/Sidebar/AppSidebar.tsx') โ
get_dependencies(file_path='frontend/src/components/Admin/UserActionsMenu.tsx') โ
get_dependencies(file_path='frontend/src/components/UserSettings/UserInformation.tsx') โ
get_dependencies(file_path='frontend/src/components/Items/columns.tsx')
Architectural Analysis
Architecture Overview
This is a full-stack monolithic application following the well-known FastAPI Full-Stack Template pattern. The backend is a layered FastAPI service (routes โ deps โ crud โ models/core) using SQLModel/SQLAlchemy with PostgreSQL, JWT auth, and SMTP email. The frontend is a React SPA using TanStack Router (file-based routing), TanStack Query for server state, and a generated OpenAPI client SDK (@hey-api/openapi-ts). The two halves communicate exclusively through a REST API contract โ the frontend's sdk.gen.ts and types.gen.ts are auto-generated from the backend's OpenAPI spec, which is a healthy contract-first boundary. The backend is cleanly layered but has a notable models.py monolith mixing DB tables, Pydantic schemas, and API DTOs in one file.
Zones of Pain
1. backend/app/models.py โ The God Module (HIGH)
This single file (backend/app/models.py:1-61) conflates four distinct concerns: database table models (User, Item at lines 49-56), Pydantic input schemas (UserCreate, UserRegister, UserUpdate, UpdatePassword at lines 22-46), API response DTOs (UserPublic, UsersPublic, ItemPublic, ItemsPublic), and shared base classes (UserBase at line 14). Every route file imports from it directly:
- backend/app/api/routes/users.py:15-26 imports 10+ model classes
- backend/app/api/routes/items.py:8 imports 7 model classes
- backend/app/crud.py:7 imports 6 model classes
- backend/app/api/deps.py:14 imports TokenPayload and User
This violates the Single Responsibility Principle and creates a change amplification problem: any schema change (e.g., adding a field to UserBase) ripples through routes, CRUD, and deps simultaneously. The file is also where TokenPayload lives โ a JWT concern that has nothing to do with database models.
2. backend/app/api/deps.py โ Auth Hub (HIGH)
deps.py is the authentication bottleneck. It defines SessionDep, TokenDep, CurrentUser, and get_current_active_superuser (backend/app/api/deps.py:26-57), and every protected route imports from it. It also directly imports engine from app.core.db (line 13) and User from app.models (line 14), coupling the auth layer to the DB engine and model layer. Any change to session management or user model ripples through every route.
3. frontend/src/hooks/useAuth.ts โ Frontend Auth Hub (HIGH)
useAuth is imported by 13 files (all routes under _layout, login/signup/recover/reset pages, and 5+ components). It bundles authentication state, sign-up/login/logout mutations, and user fetching into one hook (frontend/src/hooks/useAuth.ts:18-67). It also directly manipulates localStorage for token storage (line 15, 41-46, 56-59) โ a security-sensitive concern embedded in a UI hook. Any change to auth flow touches nearly every page in the app.
Coupling Analysis
| Module | In-Degree | Out-Degree | Concern Level | Reasoning |
|---|---|---|---|---|
backend/app/models.py |
~8 (routes, crud, deps) | ~4 (sqlmodel, pydantic) | HIGH | God module mixing DB/schema/DTO concerns; every schema change ripples everywhere |
backend/app/api/deps.py |
~5 (all route files) | ~4 (models, core.config, core.security, core.db) | HIGH | Auth bottleneck; couples auth to DB engine and model layer |
backend/app/core/config.py |
~6 (main, deps, security, utils, routes) | ~2 | MEDIUM | Module-level settings singleton (config.py:26-116) is imported everywhere; fine for config but creates implicit global state |
frontend/src/hooks/useAuth.ts |
13 | ~4 | HIGH | Auth hub for entire frontend; localStorage coupling embedded in hook |
frontend/src/client/index.ts |
3 (main, utils, tests) | 5 (core + sdk + types) | LOW | Clean auto-generated SDK boundary |
frontend/src/routeTree.gen.ts |
1 (main) | 10 (all routes) | LOW | Auto-generated; regenerated on route change |
frontend/src/components/ui/sidebar.tsx |
5 | 8 | MEDIUM | Complex shadcn/ui composite; imports 8 UI primitives |
backend/app/crud.py |
~3 (routes) | ~3 (models, security) | MEDIUM | Thin data-access layer but routes bypass it with inline SQLModel queries (see below) |
Critical finding โ dependency graph false positive: The dependency graph reports frontend/src/hooks/useAuth.ts and frontend/src/components/UserSettings/UserInformation.tsx importing backend/app/utils.py. Reading the actual source (frontend/src/hooks/useAuth.ts:1-12) confirms this is impossible โ the frontend imports from @/client and @/utils, not Python files. This is a name-collision artifact (both utils.py and utils.ts matched). The graph should be treated with caution for cross-language edges.
Design Pattern Inventory
| Pattern | Location | Consistency | Assessment |
|---|---|---|---|
Dependency Injection (FastAPI Depends) |
backend/app/api/deps.py:26-49 |
Good | SessionDep, TokenDep, CurrentUser type aliases are clean and idiomatic |
| Repository/CRUD layer | backend/app/crud.py:10-68 |
Inconsistent | CRUD exists for users/items but routes bypass it with inline SQLModel queries โ e.g., users.py:42-51 and items.py:21-42 do raw select/count in the route handler instead of delegating to crud |
| DTO/Schema separation | backend/app/models.py |
Broken | DB tables, input schemas, and response DTOs all in one file โ no schemas.py separation |
| Singleton config | backend/app/core/config.py:26-116 |
Good | Module-level settings instantiation is standard for FastAPI |
| Provider/Context (React) | frontend/src/components/theme-provider.tsx:31-106, frontend/src/components/ui/sidebar.tsx:8-20 |
Good | Context + custom hook pattern is consistent |
| Custom hooks | useAuth, useCustomToast, useCopyToClipboard, useMobile |
Good | Consistent hook-per-concern pattern |
| Generated client SDK | frontend/src/client/* |
Excellent | Auto-generated from OpenAPI; contract-first boundary is the strongest architectural decision here |
| File-based routing | frontend/src/routes/* + routeTree.gen.ts |
Good | TanStack Router convention; layout route _layout.tsx wraps authenticated pages |
Refactoring Priorities
1. Split backend/app/models.py into layered modules (HIGH impact, 1-2 sprints)
Problem: God module mixing DB tables, Pydantic schemas, and DTOs. Every schema change ripples through routes, crud, and deps.
Action: Create app/schemas/ with user.py, item.py, token.py for Pydantic models; keep app/models.py for SQLModel table classes only. Move TokenPayload to a schemas/token.py.
Benefit: Decouples API contract from persistence; enables independent evolution of DB schema vs. API DTOs; reduces merge conflicts and change blast radius.
2. Consolidate data access into crud.py (HIGH impact, 1 sprint)
Problem: Routes like users.py:42-51 and items.py:21-42 perform raw SQLModel queries inline, duplicating logic and bypassing the existing CRUD layer. The read_items handler (items.py:21-42) has a complex superuser-vs-owner branching query embedded in the route.
Action: Extract read_users, read_items, get_item_by_id into crud.py with proper pagination helpers. Routes should only handle HTTP concerns (auth, validation, response shaping).
Benefit: Single place to test query logic; routes become thin HTTP adapters; enables future caching or query optimization without touching route code.
3. Decouple useAuth from localStorage and reduce its fan-out (MEDIUM impact, 1 sprint)
Problem: useAuth is imported by 13 files and directly manipulates localStorage (useAuth.ts:15,41-46,56-59). Token storage is a security concern embedded in a UI hook.
Action: Extract token storage into a dedicated authStorage module (or use a proper auth library). Consider splitting useAuth into useUser (query) and useAuthActions (mutations) so components only pull what they need.
Benefit: Reduces re-render scope when auth state changes; makes token handling testable and auditable; lowers the blast radius of auth changes.
4. Fix the dependency graph false positives (LOW impact, quick win)
Problem: The graph reports frontend files importing backend/app/utils.py โ a cross-language impossibility caused by name collision between utils.py and utils.ts.
Action: Namespace the dependency graph by language/package boundary, or rename frontend/src/utils.ts to something more specific (e.g., errorHandlers.ts).
Benefit: Restores trust in the dependency graph as a structural analysis tool; prevents future misdiagnosis of coupling.
5. Consider extracting deps.py auth into a dedicated auth module (MEDIUM impact, 1 sprint)
Problem: deps.py couples auth to the DB engine (deps.py:13) and model layer (deps.py:14).
Action: Move get_current_user and JWT decoding into app/core/auth.py or app/services/auth.py, keeping deps.py as a thin FastAPI dependency adapter.
Benefit: Auth logic becomes testable without FastAPI dependency injection; decouples from the DB engine import; enables reuse in CLI scripts or background workers.
Overall assessment: This is a well-structured full-stack template with a strong contract-first boundary (generated SDK) and idiomatic FastAPI dependency injection. The most pressing structural debt is the models.py monolith and the inconsistent use of the CRUD layer โ both are classic symptoms of a codebase that grew from a template without a schema-separation discipline. The frontend is healthier, with the main concern being the over-centralized useAuth hook. All recommendations are achievable within single sprints and would meaningfully reduce change blast radius.