- Overview
- Architecture
- Features
- Tech Stack
- API Reference
- Data Models
- Security Implementation
- Project Structure
- Local Setup
- Environment Variables
Prescripto is a production-deployed, three-panel healthcare web application built with the MERN stack. It handles the complete appointment lifecycle β from patient registration and doctor discovery to booking, cancellation, and payment confirmation β with dedicated portals for patients, doctors, and admins.
The project was built with a focus on real backend architecture: normalized Mongoose schemas, JWT-protected routes with role-specific middleware, input sanitization using validator.js, bcrypt password hashing with salting, Cloudinary image storage via Multer memory streams, and clean RESTful API design.
prescripto/
βββ frontend/ # Patient-facing React app β Vercel
βββ admin/ # Admin + Doctor React app β Vercel
βββ backend/ # Express REST API β Render
The system runs as three independently deployed applications sharing one backend API and one MongoDB database. The frontend and admin panels communicate with the backend via REST APIs, with all protected routes gated by role-specific JWT middleware.
[ Patient Frontend ] βββββββ
ββββΊ [ Express API ] βββΊ [ MongoDB Atlas ]
[ Admin/Doctor Panel ] βββββ β
ββββΊ [ Cloudinary ]
- Register and log in with strong password enforcement (uppercase, lowercase, number, special character required)
- Browse doctors filtered by 10 specialities (General Physician, Gynecologist, Dermatologist, Pediatrician, Neurologist, Gastroenterologist, and more)
- View full doctor profiles: speciality, degree, experience, consultation fee, availability status, and about section
- Book appointments by selecting available date/time slots β booked slots are locked in real time
- View upcoming and past appointments with status (active / cancelled / completed / paid)
- Cancel appointments β slots are freed back to the doctor's availability pool automatically
- Mark appointments as paid (payment toggle with authorization check)
- Edit profile information: name, phone, address, DOB, gender
- Upload and update profile photo (stored on Cloudinary)
- Contact form with backend persistence and admin visibility
- Secure login with bcrypt-verified credentials
- View personal appointment schedule with patient details
- Mark appointments as completed
- Cancel appointments (slot freed automatically)
- Toggle availability status on/off
- Secure login with JWT-signed admin token
- Add new doctors with full profile data and photo upload (Cloudinary via Multer)
- Password strength validation: minimum length, special character, no name/email substrings
- View and manage all doctors β toggle availability, view profiles
- View all appointments across the platform β cancel any appointment
- Dashboard with live counts: total doctors, total patients, total appointments, latest 5 bookings
- View and manage all contact form messages with sort (latest/oldest) and read/unread state
| Layer | Technology |
|---|---|
| Frontend | React 19, React Router v7, Tailwind CSS, Axios, React Toastify |
| Admin Panel | React 19, React Router v7, Tailwind CSS, Axios |
| Backend | Node.js, Express.js |
| Database | MongoDB Atlas, Mongoose ODM |
| Authentication | JSON Web Tokens (JWT), bcrypt (salt rounds: 10) |
| File Uploads | Multer (memory storage) β Cloudinary |
| Input Validation | validator.js (isEmail, isStrongPassword, escape, trim) |
| Build Tool | Vite + Rolldown |
| Deployment | Vercel (frontend + admin), Render (backend) |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /register |
Public | Register new patient |
| POST | /login |
Public | Login and receive JWT |
| GET | /get-profile |
Bearer JWT |
Fetch user profile |
| PUT | /update-profile |
Bearer JWT |
Update profile + photo |
| POST | /book-appointment |
Bearer JWT |
Book appointment slot |
| GET | /appointments |
Bearer JWT |
List user appointments |
| POST | /cancel-appointment |
Bearer JWT |
Cancel + free slot |
| POST | /payment |
Bearer JWT |
Confirm payment |
| POST | /contact |
Public | Submit contact message |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /login |
Public | Doctor login |
| GET | /appointments |
dToken |
View appointments |
| POST | /complete-appointment |
dToken |
Mark completed |
| POST | /cancel-appointment |
dToken |
Cancel + free slot |
| GET | /list |
Public | List all doctors |
| POST | /change-availability |
dToken |
Toggle availability |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /login |
Public | Admin login |
| POST | /add-doctor |
aToken |
Add doctor + photo |
| GET | /all-doctors |
aToken |
List all doctors |
| POST | /change-availability |
aToken |
Toggle availability |
| GET | /appointments |
aToken |
All appointments |
| POST | /cancel-appointment |
aToken |
Cancel appointment |
| GET | /dashboard |
aToken |
Dashboard stats |
| GET | /messages |
aToken |
Contact messages |
{
name: String (sanitized),
email: String (unique, validated),
password: String (bcrypt hashed, salt 10),
image: String (Cloudinary URL),
address: { line1: String, line2: String },
gender: String,
dob: String,
phone: String
}{
name: String,
email: String (unique),
password: String (bcrypt hashed),
image: String (Cloudinary URL),
speciality: String (indexed),
degree: String,
experience: String,
about: String,
available: Boolean (default: true),
fees: Number,
address: Object,
date: Number,
slots_booked: Object (date β [times]) // sparse booking map
}{
userId: String (indexed),
docId: String (indexed),
slotDate: String,
slotTime: String,
userData: Object (snapshot),
docData: Object (snapshot),
amount: Number,
date: String,
cancelled: Boolean (default: false),
payment: Boolean (default: false),
isCompleted: Boolean (default: false)
}{
name: String (sanitized),
email: String (validated),
message: String (sanitized),
// timestamps: true (createdAt, updatedAt)
}Password Validation (Registration)
- Minimum 8 characters
- Must contain uppercase, lowercase, number, and special character (
validator.isStrongPassword) - Hashed with
bcrypt.genSalt(10)before storage β plain text never persisted
Password Validation (Add Doctor β Admin)
- Custom strength checker: minimum length, special character required
- Blocks passwords containing substrings of the doctor's name or email
Input Sanitization
- All user-submitted strings passed through
validator.escape()+validator.trim()before DB writes - Email validation via
validator.isEmail()on all relevant endpoints
JWT Middleware
- User routes:
Authorization: Bearer <token>header, decoded toreq.user.userId - Doctor routes:
dtokenheader, decoded toreq.doctor.docId - Admin routes:
atokenheader, signed payload verified againstADMIN_EMAIL + ADMIN_PASSWORD
Authorization Checks
- Payment endpoint verifies
appointmentData.userId === req.user.userIdbefore marking paid - Cancellation endpoints verify ownership before slot manipulation
prescripto/
β
βββ backend/
β βββ server.js # Express app, CORS, routes
β βββ config/
β β βββ mongodb.js # Mongoose connection
β β βββ cloudinary.js # Cloudinary SDK config
β βββ controllers/
β β βββ userController.js # Patient logic (14 handlers)
β β βββ doctorController.js # Doctor logic
β β βββ adminController.js # Admin logic + dashboard
β βββ middleware/
β β βββ authUser.js # Bearer JWT verification
β β βββ authAdmin.js # Admin token verification
β β βββ multer.js # Memory storage for Cloudinary
β βββ models/
β β βββ userModel.js
β β βββ doctorModel.js
β β βββ appointmentModel.js
β β βββ contactModel.js
β βββ routes/
β βββ userRoute.js
β βββ doctorRoutes.js
β βββ adminRoutes.js
β
βββ frontend/ # Patient-facing app
β βββ src/
β βββ pages/ # Home, Doctors, Appointments, Profile, etc.
β βββ components/ # NavBar, Header, TopDoctors, Banner, etc.
β βββ context/AppContext.jsx # Global state
β
βββ admin/ # Admin + Doctor panel
βββ src/
βββ pages/
β βββ Admin/ # AddDoctor, AllAppointments, DoctorsList, Messages
β βββ Doctor/ # DoctorAppointments
βββ components/ # NavBar, SideBar
βββ context/ # AdminContext, DoctorContext, AppContext
Prerequisites: Node.js v18+, MongoDB Atlas URI, Cloudinary account
git clone https://github.com/Soham-Lodh/Prescripto
cd Prescriptocd backend
npm install
# Add your .env file (see Environment Variables below)
node server.js
# Runs on http://localhost:4000cd ../frontend
npm install
npm run dev
# Runs on http://localhost:5173cd ../admin
npm install
npm run dev
# Runs on http://localhost:5174Create a .env file in /backend:
MONGODB_URI=your_mongodb_atlas_connection_string
JWT_SECRET=your_jwt_secret_key
CLOUDINARY_NAME=your_cloudinary_cloud_name
CLOUDINARY_API_KEY=your_cloudinary_api_key
CLOUDINARY_SECRET_KEY=your_cloudinary_api_secret
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=your_admin_passwordCreate a .env file in /frontend and /admin:
VITE_BACKEND_URL=http://localhost:4000Built by Soham Lodh Β· MIT License