Architecture
How temetro is put together — frontend, backend, database, and real-time.
temetro is a monorepo with two independent apps that talk over HTTP and WebSockets:
The pieces
| Layer | Technology | Notes |
|---|---|---|
| Frontend | Next.js 16 (App Router), React 19, Tailwind v4 | COSS / Base UI components; chat UI built on custom ai-elements |
| Backend | Node.js ≥ 20, Express 5, TypeScript | REST API under /api/*, plus Socket.io for real-time |
| Database | PostgreSQL 17, Drizzle ORM | Migrations generated by drizzle-kit, applied automatically on startup |
| Auth | Better Auth | Email/password + username plugin; organization plugin = clinics; cookie sessions |
| Real-time | Socket.io | Messages and notifications pushed live; authenticated by the session cookie |
Request flow
- The browser talks to the frontend (
:3000), which renders the UI. - The frontend's
lib/api-client.tscalls the backend (:4000, configured byNEXT_PUBLIC_API_URL) with the session cookie attached; a401bounces the user to/login. - Every
/api/*route requires a signed-in user and an active organization (clinic). Handlers check the role-based permissions defined insrc/lib/access.ts— the same matrix shown in Roles & permissions, enforced server-side. - Mutations write an entry to the activity log and create notifications, which Socket.io pushes to connected clients.
Database tables
| Group | Tables |
|---|---|
| Patients | patients, plus position-ordered satellites: patient_allergies, patient_medications, patient_problems, patient_labs, patient_encounters |
| Clinical | appointments, prescriptions, tasks, notes |
| Messaging | conversations, conversation_participants, messages |
| System | activity_log, notifications |
| Auth (Better Auth) | user, session, account, organization, member, invitation |
Every clinic-owned table carries an organizationId — multi-tenancy is enforced by
scoping every query to the caller's active organization.
Multi-tenancy & auth in one paragraph
Better Auth's organization plugin models clinics. A user signs up, then either
creates an organization (becoming owner) or joins one via invitation. The session
tracks an active organization; the backend resolves it on every request and scopes
all reads and writes to it. Roles (owner, admin, doctor, reception,
pharmacy, lab, member) and their permission statements (patient,
appointment, prescription, task, lab) live in backend/src/lib/access.ts,
with a mirror in frontend/lib/access.ts so the UI can hide what the server would
reject. The frontend derives its route gating from the same permissions: full
clinicians are probed via prescription:delete, the pharmacy area via
prescription:write, and the lab area via lab:write.