Re-Medi
Healthcare platform with appointments and role-separated access.
Python · REST APIs · MongoDB · JWT
Overview
Re-Medi handles appointments between patients and clinic staff. Different roles see different things, which made access control the central problem rather than an afterthought.
It was the first project where I implemented JWT authentication and role-based access control myself instead of reaching for a framework that hid them.
Architecture
A REST API in front of MongoDB, with authorisation applied as a dependency so every protected route declares what it needs.
Client
Separate views for patients and staff.
REST API
Routing and request validation.
JWT auth
Role checks
Identity from the token, permission from the role.
MongoDB
Users, appointments, and records as documents.
Listing appointments
1. GET /appointments
The same endpoint serves patients and doctors; the caller decides what it returns.
2. Verify the token
The JWT is decoded and rejected if expired, before any handler logic runs.
3. Read the role
Identity comes from the token and permission from the role, never from a query parameter.
4. Scope the query
Patients get their own rows, doctors get their schedule. The filter is derived, not supplied.
Database schema
users
Patients and staff in one collection, separated by role.
- _id
- ObjectId
- string · unique index
- passwordHash
- string
- role
- string · patient, doctor, admin
- createdAt
- Date
appointments
A booking between a patient and a doctor.
- _id
- ObjectId
- patientId
- ObjectId · reference to users
- doctorId
- ObjectId · reference to users
- slot
- Date · unique index with doctorId
- status
- string · requested, confirmed, cancelled
API design
- POST/auth/registerCreate a patient account.
- POST/auth/loginIssue a JWT.
- GET/appointmentsauthScoped to the caller: patients see theirs, doctors see their schedule.
- POST/appointmentsauthRequest a slot.
- PATCH/appointments/{id}authConfirm or cancel. Staff only.
Challenges
Authorisation scattered across handlers
Role checks written inline in each route were easy to forget. Moving them into a reusable dependency made the requirement visible in the route signature instead of buried in its body.
Leaking other people’s data through filters
A list endpoint that trusted a query parameter for the patient id would happily return someone else’s appointments. Scoping every query by the caller, not by input, fixed it.
Lessons learned
- Never let the client tell you who it is; derive it from the token.
- Role checks belong in one place, declared per route.
- Authentication is the easy half. Authorisation is where the bugs live.