This document summarizes architectural observations, risks, and concrete recommendations to improve maintainability, performance, and deployment reliability.
- Stack: FastAPI + Strawberry (GraphQL), SQLAlchemy, Postgres; React + Vite + Apollo Client; Vercel deployment.
- Structure separation is good (backend
app/, serverless entryapi/main.py, frontendfrontend/).
- CORS: Currently
allow_origins=["*"]withallow_credentials=True. In production, restrict origins (your Vercel domain) and/or setallow_credentials=Falseif not using cookies. - Auto table creation:
Base.metadata.create_all()exists in bothmain.pyandapi/main.py. Remove in production and rely on Alembic migrations to reduce cold start and ensure schema parity. - Duplicate schema definition:
app/graphql/schema.pyis the source of truth. Drop the extraschema = strawberry.Schema(...)at bottom ofapp/graphql/resolvers.py.
- Serverless connection strategy: Intention is NullPool, but
poolclass=Nonedoes not enforce it. Usefrom sqlalchemy.pool import NullPooland setpoolclass=NullPoolincreate_engine. - Cascades:
- ORM-level cascade exists:
Sensor.readingshascascade="all, delete-orphan". - Add DB-level cascades: set
ondelete="CASCADE"on FKs (e.g.,api_sensor_readings.sensor_id→api_sensors.id). Considerpassive_deletes=Trueon relationships to leverage DB cascades. - After DB-level cascades, simplify
delete_sensormutation (no manual child deletes).
- ORM-level cascade exists:
- SQLAlchemy modern API: Use
from sqlalchemy.orm import declarative_baseinstead ofsqlalchemy.ext.declarative.declarative_base.
- N+1 queries:
Sensor.sensor_type,Sensor.location, andSensorReading.sensortrigger extra queries per row. Adopt DataLoader or appropriate eager loading. - Consistency:
types.pycontains@strawberry.fieldmethods for nested fields, whileresolvers.pyreassigns resolvers dynamically. Pick one approach (recommend: methods intypes.py) and remove dynamic reassignment. - Validation:
create_sensorshould validatesensor_type_idandlocation_idexistence; return clear errors.- Use Strawberry
EnumforAlert.status/severityto prevent invalid values.
- Pagination/limits:
sensor_readingsuses limit/offset without a max cap. Enforce a safe maximum (e.g., 1000) and consider cursor-based pagination for time-series.
- Unimplemented fields:
Sensor.latest_readingandAlert.readingreturnNone. Implement resolvers or remove these fields.
- Minor: Remove unused imports (e.g.,
and_,Session) fromresolvers.py.
SECRET_KEYhas a default. Fail fast ifENVIRONMENT=productionand default is used.- Ensure Vercel env vars are complete:
DATABASE_URL,SECRET_KEY,ENVIRONMENT=production,DEBUG=false.
- Endpoint logic: Good. Prod uses relative
/graphql; dev usesVITE_GRAPHQL_ENDPOINT. - Error UI: With
errorPolicy: 'all', ensure components surfaceerrorand partial data states clearly. - Types/state: Guard for
undefineddata shapes in pages.
- Current
vercel.json:- Builds frontend:
buildCommand: "cd frontend && npm ci && npm run build"andoutputDirectory: "frontend/dist". - Rewrites
/graphql,/api/(.*),/health→/api/main(good).
- Builds frontend:
- SPA fallback for React Router (avoid 404 on deep links):
- Add filesystem-first rule and index fallback, e.g. in
routesformat:{ "handle": "filesystem" }{ "src": "/(.*)", "dest": "/index.html" }
- If staying with
rewrites, ensure assets resolve before catch-all and add a final rewrite to/index.html.
- Add filesystem-first rule and index fallback, e.g. in
- Explicit Python runtime for functions:
"functions": { "api/**/*.py": { "runtime": "python3.11" } }to avoid runtime version errors.
- Dependencies: Ensure the runtime installs the correct requirements (unify on
requirements.txtor configure Vercel to userequirements-vercel.txt). - CORS: Since FE/BE are same origin in prod, restrict CORS to your domain.
- Use Alembic migrations for all schema changes (ondelete cascades, new indexes) and remove
create_allin serverless. - Add unit tests for resolvers and integration tests for CRUD flows.
- Add CI for Python (ruff/black/mypy) and frontend (ESLint/TypeScript build).
- Index coverage is good for time-series. Consider retention strategies and partitioning if volume grows.
- Add structured request/DB logging and basic metrics.
- Consider simple caching for heavy read queries if applicable.
- Do not commit real
.envvalues. Ensure secrets live in Vercel. - Tighten CORS in prod.
- Validate inputs server-side (IDs, numeric bounds) to avoid bad writes.
- Switch to
NullPoolincreate_enginefor serverless reliability. - Add SPA fallback in
vercel.jsonto fix React Router 404s. - Remove
create_allfromapi/main.py; rely on Alembic migrations. - Remove duplicate
schemaand dynamic field resolver assignments; use a single approach. - Add DB-level
ondelete="CASCADE"andpassive_deletes=True; then simplifydelete_sensor. - Restrict CORS in production.
- I can apply (1) and (2) immediately, then stage the cascade + migration work (5) in a separate PR to avoid production disruption.