Backend Overview
Backend overview: API route tree, shared helpers, auth pattern
Backend Overview
This page provides an overview of the backend architecture, focusing on the API route tree, shared helper functions, and the authentication pattern.
API Route Tree
The API route tree is structured exclusively around the App Router, with no pages/ directory. Authentication-related pages reside within the (auth) route group, which shares the same URL space but utilizes its own layout and does not introduce a /auth prefix in the URL. Both the dashboard and client portal feature their own layouts, each protected by server-side authentication guards. The public booking page (/p/[slug]) is implemented as an async Server Component that performs its database reads server-side before rendering and handing off interactivity to the BookingWidget client component.
There is no centralized middleware.ts for authentication. Instead, every route that requires authentication explicitly calls supabase.auth.getUser() itself via the getUser() and getProfessional(userId) helpers in src/lib/api.ts.
Shared Helpers
Shared helpers for API routes are defined in src/lib/api.ts. These helpers standardize JSON responses, authenticate users, and extract client IPs.
The src/lib/api.ts module provides:
- Response helpers: Functions such as
ok,unauthorized,forbidden,notFound,badRequest,conflict,tooManyRequests, andserverErrorreturn standardized JSON responses with appropriate HTTP status codes. TheserverErrorhelper logs the error server-side and returns a generic 500, preventing internal details from being exposed to the client. - Auth helpers:
getUser()retrieves the authenticated Supabase user by callingsupabase.auth.getUser()viacreateClient()fromsrc/lib/supabase/server.getProfessional(userId)fetches the corresponding professional record from theprofessionalstable insrc/lib/db/schemausingdbfromsrc/lib/db.
- Utility helpers:
getIp(req)extracts the client IP from thex-forwarded-forrequest header, primarily for rate limiting purposes.
Auth Pattern
The authentication pattern relies on Supabase for end-to-end authentication.
src/lib/supabase/server.tswraps the Supabase server client usingcreateServerClientand a Next.js cookie adapter (leveragingcookies()fromnext/headers). Thetry/catchblock aroundsetAllhandles the known "RSC cannot write cookies" error as per the Supabase SSR pattern.- A browser-side equivalent client is used for operations like sign-out and signup.
- The register route utilizes the anonymous Supabase client in production to trigger verification emails.
- Smart-linking (backfilling
clients.userIdby email) is gated within/auth/callbackto ensure it executes only afterexchangeCodeForSessionhas successfully verified the user's inbox.
Known Gaps
A notable design choice is the absence of a centralized middleware for authentication. Instead, every route that requires authentication must explicitly call supabase.auth.getUser() itself via the getUser() and getProfessional(userId) helpers in src/lib/api.ts. While this approach is explicit, it places the burden of authentication on each new route author and has been flagged by a security audit as a potential area for improvement.
Why This Shape
The backend is structured around the App Router to leverage its server-side rendering and authentication capabilities effectively. The shared helpers in src/lib/api.ts provide a standardized and consistent way of handling API responses, authentication, and common utility functions across all routes. The authentication pattern relies on Supabase to manage end-to-end authentication, with a strong focus on verifying user inboxes and enabling smart-linking. This architectural shape aims for a flexible and scalable backend that can adapt to evolving requirements.