Every route this API exposes, cross-referenced against the frontend's endpoint constants (apps/web/src/services/ApiEndpoints.ts) and call sites (apps/web/src/services/MilanApi.ts and per-feature code). Read this before changing any route's path, method, or request/response shape — and before assuming a frontend call "just works" against this backend.
Snapshot taken August 2026, from static reading of both apps — not verified by running either app end-to-end. Treat every "works"/"broken" verdict below as a strong static-analysis claim, not a confirmed-live-tested fact; reproduce with a real request before shipping a fix that assumes one of these is correct.
Update: GET /user, GET /organizations, GET /display/users, GET /display/organizations, GET /events, and GET /product/allproducts (their unfiltered "list everything" branches only) now return { data, pagination } instead of a bare array — see known-issues.md#pagination and architecture.md#pagination. This was verified live, via a Supertest assertion against a real (in-memory) Mongo, not just read from source — see tests/events.test.ts's "paginates across multiple pages with skip/limit math" case.
Update: All four contract breaks below are resolved — PATCH /user/update, GET /user/profile, GET /auth/login/success, and PATCH /user/complete all now work as the frontend already calls them. See known-issues.md#cross-repo-contract-breaks and users.md/auth.md for the fix detail; the breakdown below is kept for historical context on what was wrong and why.
| Method + path | Module | Auth | Frontend constant | Frontend actually calls it? |
|---|---|---|---|---|
GET / |
app.ts | no | — | no (liveness check only) |
GET /health |
app.ts | no | — | no |
GET /docs |
app.ts (swagger) | no | — | no |
POST /auth/signup |
auth | no | authEndpoints.signup |
✅ RegisterUser() |
POST /auth/signin |
auth | no | authEndpoints.signin |
✅ LoginUser() |
GET /auth/check-email |
auth | no | authEndpoints.checkEmail |
✅ CheckEmailExists() — called from SignUp.tsx's step-1 "Continue" |
POST /auth/update |
auth | no (email+oldPassword in body) | — | ❌ no frontend constant/call exists for this route at all |
GET /auth/google |
auth | no | authEndpoints.googleLogin |
✅ GoogleAuth() |
GET /auth/google/callback |
auth | Passport | — | n/a (hit by Google's redirect, not called directly by frontend code) |
GET /auth/login/failed |
auth | no | — | n/a (redirect target, not called directly) |
GET /auth/login/success |
auth | requireAuth (see auth.md) |
authEndpoints.googleLoginSuccess |
✅ successCallback() — was unreachable, fixed, see contract break #3 |
GET /auth/logout |
auth | no | authEndpoints.logout |
✅ Logout() |
GET /user |
users | no | userEndpoints.details(userName) |
✅ (per frontend spec, various profile-lookup call sites) |
GET /user/profile |
users | requireAuth |
userEndpoints.profile |
✅ Dashboard.tsx's SWR hook — was 404, fixed, see contract break #2 |
PATCH /user/update |
users | requireAuth |
userEndpoints.updateProfile |
✅ updateUserProfile() — was a method mismatch, fixed, see contract break #1 |
| — (no route) | — | — | userEndpoints.update (/user/update/profile) |
❌ dead constant on the frontend side, no matching backend route either — consistent dead code both sides, not a break |
PATCH /user/complete |
users | requireAuth |
userEndpoints.completeProfile |
✅ useProfileCompletion.ts/ProfileCompletion.tsx — route did not exist, fixed, see contract break #4 |
POST /user/report |
reports | no | userEndpoints.report |
✅ ReportProblem() |
GET /organizations |
organizations | no | organizationEndpoints.all, organizationEndpoints.details(userName) |
✅ GetAllOrganizations() (only from the unrouted Donate.tsx — see the pagination note above), and per frontend spec, Profile.tsx's SWR key (single-item branch, unaffected by pagination) |
GET /organizations/dashboard |
organizations | requireAuth |
organizationEndpoints.dashboard |
✅ fetchDashboard() |
| — (no route) | — | — | organizationEndpoints.createEvent (/organization/createevent) |
❌ dead constant, no backend route either — consistent dead code both sides |
GET /display/users |
directory | no | — | ❌ no frontend constant exists for this route |
GET /display/organizations |
directory | no | — | ❌ no frontend constant exists for this route |
GET /events |
events | no | eventEndpoints.all |
per frontend spec, not currently called by any live SWR/fetch (the fetcher exists — getEvents() — but the live Events.tsx page renders hardcoded data instead; see the frontend's own known-issues.md) |
POST /events/create |
events | requireAuth |
eventEndpoints.create |
✅ CreateEvent() — method/path match; body-shape correctness depends on which of the frontend's two competing "create event" components made the call, see events.md |
POST /payment/razorpay |
payments | no | — (no paymentEndpoints constant exists in ApiEndpoints.ts) |
frontend's PaymentGateway.ts calls this URL directly, not through the ApiEndpoints.ts registry — see the frontend's own donate-shop-trending.md/SPEC.md for the exact call site and its own separate response-shape bug (data.currency/data.id vs. this route's actual {id, currency, amount} wrapped in axios's response.data) |
POST /product/addproduct |
products | no | — | no productEndpoints constant exists on the frontend at all — the frontend's Shop/product pages are "coming soon" placeholders per its own spec, so this whole module currently has no frontend consumer |
GET /product/allproducts |
products | no | — | same as above — no consumer yet |
POST /product/cart/add |
products | requireAuth (was unauthenticated — see known-issues.md#products) |
— | same as above — no consumer yet |
GET /product/:productSlug |
products | no | — | same as above — no consumer yet |
The frontend's updateUserProfile() (MilanApi.ts) issues Axios.patch(userEndpoints.updateProfile, credentials, { withCredentials: true }). This backend used to register the handler as router.post("/update", ...) (user.routes.ts) — Express only matches the declared method, so a PATCH to /user/update fell through to notFoundHandler (404) instead of the intended controller, breaking the live "edit my profile" flow both ProfileCompletion.tsx and ProfileUpdate.tsx use. Fixed by changing the route to router.patch("/update", ...) — matches what the frontend already sends, no frontend change needed. The old POST registration was removed rather than kept alongside it (nothing else called it).
updateProfileSchema used to validate { tagLine?, description?, city?, state?, address?, country?, pincode? } as flat top-level strings — none of which lined up with IUser.address's nested object, and tagLine didn't exist on the schema at all. Fixed in the same change as #1: updateProfileSchema now validates { name?, description?, coverImage?, address?: { line1?, line2?, city?, state?, country?, pincode? } }, matching exactly what ProfileUpdate.tsx sends. coverImage (the frontend's name for the field) is translated onto IUser.bannerPicture in user.service.ts's toUserUpdate rather than the schema growing a field named after the frontend's UI copy. See users.md.
The frontend's Dashboard.tsx (per its own api-integration.md) calls useSWR(userEndpoints.profile, fetcher), i.e. GET /user/profile — no route matched this before, so the call 404d every time. Fixed by adding a real GET /user/profile route (requireAuth-gated, looks the caller up by req.auth.email, the same pattern GET /organizations/dashboard already used), responding { user: <sanitized> } — the wrapper shape Dashboard.tsx already reads (profileData?.user), not the bare object GET /organizations/dashboard returns. GET /organizations/dashboard (fetchDashboard()) is unaffected and still works exactly as before — this was an addition, not a redirect of one call site to the other.
See auth.md for the full trace. The route's controller used to require req.user, populated by Passport during the earlier /auth/google/callback request — but session: false means nothing carries that forward to this later, separate request, so it 401d every time in real traffic. Fixed by moving session issuance (signing the JWT, setting Token/userName/isLoggedIn/userType) into googleCallback itself — the one request that legitimately has req.user — and making loginSuccess a normal requireAuth-gated route that reads the Token cookie googleCallback already set on its redirect response. Verified live via Supertest (tests/auth.test.ts), confirming the route is reachable given a valid session cookie and still 401s without one.
The frontend's completeProfileApiCall() (MilanApi.ts) calls Axios.patch(userEndpoints.completeProfile, credentials, { withCredentials: true }), i.e. PATCH /user/complete — no route existed for this at all, and config.hasCompletedProfile (see users.md) could never become true through any code path in this API. Fixed by adding PATCH /user/complete (requireAuth-gated, same body shape as PATCH /user/update), whose service layer accepts the profile fields and always sets config.hasCompletedProfile: true server-side on success — independent of whatever the client's body claims about that field.
GET /display/users, GET /display/organizations (directory module), and the entire products module (/product/*) have no corresponding call anywhere in the frontend's ApiEndpoints.ts/MilanApi.ts. This isn't necessarily a bug — the frontend's Shop/Trending pages are explicitly "coming soon" placeholders per its own spec — but it means these routes are currently untested by any real client traffic pattern; if you change their contract, there's no frontend code that would visibly break to catch a regression, only this doc and (for products) whatever new frontend work eventually lands.
If you fix one of the contract breaks above (on either side of the repo boundary), update both repos' specs in the same change: this file plus the relevant module file here, and the frontend's own docs/specs/api-integration.md plus its known-issues.md entry if one exists for the same issue. A contract break fixed on only one side, with the other side's spec still describing the old broken behavior, is worse than not documenting it at all — the next agent will trust the stale doc.