System overview
The shape of it
Schoolers is a single web application that talks directly to a self-hosted Supabase instance. There is no separate API service in between, and no backend written by this project beyond a thin production server and a few server functions.
Browser
|
| HTTPS
v
Traefik (reverse proxy, TLS termination)
|
+------------------> Schoolers app (Node, serves SSR + static assets)
| |
| | HTTPS, as the signed-in user
| v
+------------------> Kong (Supabase API gateway)
|
+--------------+--------------+---------------+
v v v v
PostgREST GoTrue Storage Studio
(the database (sign-up, (photos, (admin UI,
as a REST API) sessions) post images) owners only)
| | |
+--------------+--------------+
v
PostgreSQL
(tables, and the row-level
security policies that are
the real access boundary)Everything in that diagram runs on one virtual server. See What runs where for the physical picture.
Why there is no backend service
The application queries PostgreSQL through PostgREST, which exposes tables as a REST API automatically. A call like supabase.from("profiles").select("*") becomes a real SQL SELECT, executed as the role the caller authenticated with.
That works safely only because PostgreSQL itself decides what each caller may see. Had access control been written in application code, skipping the application would skip the checks. Instead the rules live in the database, which nothing can route around.
The practical consequence for you: a new query is not finished until a policy allows it. Queries do not fail loudly when a policy is missing. They return an empty result, which looks exactly like "no data" and is easy to misdiagnose as a frontend bug.
The parts this project actually owns
The React application under src/. TanStack Start provides server-side rendering and file-based routing; TanStack Query handles data fetching and caching; the UI is Tailwind CSS with shadcn/ui components. See Routing and rendering.
The database schema under supabase/migrations/. Every table, policy, function, and trigger this project depends on is defined in those files. See Data model.
A production server, serve.mjs. The build emits a web-standard fetch handler rather than a listening server, so this file hosts that handler on Node and serves the built client assets alongside it.
A small amount of server-side code in src/integrations/supabase/, including middleware that validates a bearer token on server-function calls and attaches the caller's identity.
Everything else in the diagram is Supabase's own software, deployed as provided.
What the application does not do
Worth knowing so you do not go looking for it:
- No messaging or chat between members.
- No automated identity verification. Profile approval is a human decision, by design.
- No multi-faculty support. The directory covers one school.
- No email delivery. Outbound mail is blocked at the hosting provider, so sign-ups skip email verification entirely. This is a known compromise, not an oversight.
Bilingual from the ground up
The interface is English and Arabic, and Arabic renders right-to-left. This is not a translation layer bolted on afterwards: text direction flips at the document level, and layout uses direction-aware CSS properties throughout.
The practical rule when writing UI code: never hardcode a user-facing string, and prefer logical CSS properties (start/end) over physical ones (left/right). A component that looks correct in English and broken in Arabic is a bug, and it will be noticed, because a large share of members use Arabic.