Skip to content

perf(cache): add HTTP Cache-Control + ETag for stable GET endpoints #99

@AndresL230

Description

@AndresL230

Problem

Backend GET endpoints serve fresh JSON on every request even when the underlying data hasn't changed. The frontend has no client-side response cache (no React Query, SWR, or similar in frontend/src/lib/api.ts), so navigations and re-renders hit the API repeatedly. The only cache-aware behavior today is the avatar ?v=<timestamp> cache-bust in frontend/src/context/UserContext.tsx:127.

Proposal

Add Cache-Control + ETag to user-scoped GETs that are stable for short windows. Conditional If-None-Match returns 304 Not Modified and skips both DB work and JSON serialization.

Initial targets:

  • GET /api/study-guide/{user_id}/cached (routes/study_guide.py:117) — ETag from max generated_at.
  • GET /api/social/rooms/{room_id}/summary (routes/social.py) — ETag from the existing member_hash already stored in room_summaries.
  • GET /api/profile/{user_id} and course-context reads — ETag from updated_at.

Headers:

  • Cache-Control: private, max-age=30, stale-while-revalidate=60 (private because most data is user-scoped and decrypted at the edge).
  • ETag: \"<hash>\", validate against If-None-Match.

Frontend:

  • Adopt React Query (or TanStack Query) in frontend/src/lib/api.ts so in-flight requests dedupe and the browser HTTP cache + ETag flow is actually exercised. Without a client cache layer, Cache-Control only helps the browser memory cache between identical URL hits, not React re-renders.

Acceptance criteria

  • Small FastAPI dependency / response helper for setting ETag + handling If-None-Match (returns Response(status_code=304)).
  • At least 3 endpoints converted, with private directive everywhere user data is involved.
  • Never cache responses that contain decrypted sensitive columns at a shared layer (CDN/proxy). Document this in CLAUDE.md Gotchas.
  • Frontend: introduce React Query and migrate one screen (e.g., Study) as a proof-of-concept.
  • Logs/metrics show 304 rate per endpoint.

Tradeoffs / risks

  • Encryption gotcha: per CLAUDE.md, several columns are decrypted before reaching the response. Cache-Control must be private (never public) on these routes — flag this prominently.
  • ETag generation cost must be lower than full response build (use updated_at/existing hashes, don't re-hash full payload).
  • React Query is a real frontend dependency; consider scope before adopting repo-wide.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions