Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/bentlyy/Clinica/llms.txt

Use this file to discover all available pages before exploring further.

Clinica is built as a modular monolith: a single Node.js process organized into self-contained feature modules, each responsible for its own routing, business logic, and data access. This approach keeps the codebase navigable and cohesive while avoiding the operational overhead of microservices. The backend exposes a REST API consumed by a React single-page application, and the entire system — API, frontend, and database — runs as a three-service Docker Compose stack.

Tech stack

LayerTechnology
Backend runtimeNode.js (ESM) + Express 5
DatabasePostgreSQL 15 via pg connection pool
AuthenticationJWT (jsonwebtoken) + bcrypt password hashing
EmailNodemailer (Gmail transport)
Background jobsnode-cron
FrontendReact 19 + Vite + React Router v7 + MUI v9
ContainerizationDocker Compose 3.8

Backend modules

Each module lives under src/modules/<name>/ and contains exactly three files: a routes file that registers Express handlers, a controller that parses requests and sends responses, and a service that holds all business logic and database queries.

auth

Handles user registration, login, and JWT issuance. Passwords are hashed with bcrypt before storage. The issued token carries the user’s id and role.

doctor

CRUD operations for doctor profiles. Links each doctor record to a users row via user_id, allowing doctors to authenticate with the same credential system as patients.

booking

Creates, lists, and manages appointments. Enforces the unique_booking constraint (one booking per doctor per date+time slot) and validates that bookings are not placed in the past.

availability

Manages a doctor’s recurring weekly schedule (doctor_availability) and one-off blocked periods (doctor_exceptions). The frontend uses this data to render only valid time slots.

API route prefixes

All routes are mounted in src/app.js under the following prefixes:
PrefixModule
/api/authauth
/api/doctorsdoctor
/api/bookingsbooking
/api/availabilityavailability
A GET /health endpoint queries the database with SELECT 1 and returns { status: "ok", db: "connected" } when healthy.

Shared services

Two utilities in src/shared/ are imported by any module that needs them, keeping infrastructure concerns out of feature code. shared/db.js — exports a single pg.Pool instance configured from the DATABASE_URL environment variable. All modules query through this shared pool; there is no per-module connection management. shared/email.service.js — exports a sendEmail({ to, subject, html }) function backed by a Nodemailer Gmail transport. Credentials are read from EMAIL_USER and EMAIL_PASS environment variables.

Background job

The reminder job starts automatically after the server boots and runs for the lifetime of the process. It does not require a separate worker process or queue.
src/jobs/reminder.job.js schedules a cron task with the expression */5 * * * * (every five minutes). On each tick it executes two queries against the bookings table:
  • 1-hour reminder — finds bookings whose date + time falls between 55 and 65 minutes from now and where reminder_1h_sent = FALSE. Sends an email to the patient, then sets reminder_1h_sent = TRUE.
  • 24-hour reminder — same pattern with a 23–25 hour window and the reminder_24h_sent flag.
Both flags are stored on the bookings row so reminders are sent exactly once even if the job runs multiple times within the window.

Database schema

The five tables are created by db/init.sql, which PostgreSQL executes on first container start via the docker-entrypoint-initdb.d mount.

users

ColumnTypeNotes
idSERIALPrimary key
emailTEXTUnique, not null
passwordTEXTBcrypt hash
roleTEXTDefaults to 'user'; doctors and admins use different values
created_atTIMESTAMPDefaults to current time

doctors

ColumnTypeNotes
idSERIALPrimary key
nameTEXTNot null
specialtyTEXTNot null
emailTEXTOptional display field; authoritative email lives in users
user_idINTUnique FK → users(id), ON DELETE SET NULL

bookings

ColumnTypeNotes
idSERIALPrimary key
doctor_idINTFK → doctors(id), ON DELETE CASCADE
user_idINTFK → users(id), ON DELETE CASCADE
dateDATEMust be ≥ CURRENT_DATE
timeTIMECombined with date for uniqueness per doctor
durationINTMinutes; defaults to 30, max 480
reminder_1h_sentBOOLEANTracks whether the 1-hour email has been sent
reminder_24h_sentBOOLEANTracks whether the 24-hour email has been sent
created_atTIMESTAMPDefaults to current time
A UNIQUE (doctor_id, date, time) constraint prevents double-booking the same slot.

doctor_availability

ColumnTypeNotes
idSERIALPrimary key
doctor_idINTFK → doctors(id), ON DELETE CASCADE
day_of_weekINT0 = Sunday, 6 = Saturday
start_timeTIMEMust be before end_time
end_timeTIMEChecked via CHECK (start_time < end_time)

doctor_exceptions

ColumnTypeNotes
idSERIALPrimary key
doctor_idINTFK → doctors(id), ON DELETE CASCADE
dateDATEThe specific date being blocked
start_timeTIMENullable; used for partial-day blocks
end_timeTIMENullable; used for partial-day blocks
is_full_dayBOOLEANWhen TRUE, the entire day is unavailable regardless of start_time/end_time
Indexes on doctor_id, user_id, and date columns across all three appointment-related tables keep query performance consistent as data grows.

Frontend pages

The React application is served by Vite on port 5173. React Router v7 handles client-side navigation, and MUI v9 provides the component library.
PageRoute (inferred)Role
LoginPage/loginCredential form; stores JWT in context
DoctorsPage/doctorsBrowse and search available doctors
MyBookingsPage/bookingsPatient view of upcoming and past appointments
DoctorPanel/doctorDoctor’s own appointment dashboard
DoctorAvailabilityPage/doctor/availabilitySet recurring weekly schedule and exceptions
DoctorCalendarPage/doctor/calendarVisual calendar of booked slots

Docker Compose topology

services:
  db:       # postgres:15, port 5432 (internal)
  api:      # Node.js backend, port 3000
  frontend: # Vite dev server, port 5173
api declares depends_on: db and polls the database with SELECT 1 every two seconds before seeding the admin user and starting the HTTP listener. frontend declares depends_on: api. The database volume (db_data) persists across container restarts.
init.sql runs only once — when the db_data volume is first created. To re-run it, remove the volume with docker compose down -v before starting the stack again.

Build docs developers (and LLMs) love