MedDonate — Medicine Donation Platform
Connecting people and NGOs who have spare, unexpired medicine with people who need it — rebuilt from PHP to a typed three-tier app.
- Role
- Sole developer
- Team
- Solo
- Timeline
- Sep 2025 – Sep 2026
- Organisation
- Course project (CSE309) → personal rebuild
- Code
- Public repository
- Reading time
- 2 min
Started as an individual course project in PHP/MySQL, then rebuilt as React 19 + Express 5 + PostgreSQL 16 with transactional stock control, OTP password reset and NGO verification.
Overview
Unused medicine expires in drawers while others can't afford it. MedDonate lets individuals and NGOs donate unexpired medicine, request what they need, and lets admins moderate a shared inventory.
The problem
- Donations and requests compete for the same stock.
- NGOs must be verified before they can request on behalf of beneficiaries.
- The first version (a CSE309 project, Sep–Dec 2025) worked, but approvals weren't transactional and password reset lived in the browser.
Context & constraints
Version 1 was an individual web-applications course project in PHP 8 and MySQL with a full SRS: use-case, DFD, sequence, activity, class and ER diagrams. Version 2 rebuilds it as a typed three-tier app.
Approach
- Roles — individuals are approved on sign-up; NGOs upload a proof document and wait for admin approval; admins moderate everything.
- Stock you can't oversell — approving a request runs in a transaction that locks the request and inventory rows (
SELECT … FOR UPDATE) and fails loudly with a 409 if stock is short. Approving a donation upserts inventory. - Safer auth — JWT in an httpOnly cookie; OTP reset codes stored as bcrypt hashes with expiry.
- Bangladesh-aware — division and district pickers covering all 8 divisions and 64 districts.
- Operations — delivery assignment with status tracking, user blocking and a contact inbox.
Key capabilities
- Individual, NGO and admin roles
- NGO proof-document verification
- Transactional inventory with row locks
- OTP password reset with hashed codes
- Delivery assignment and tracking
- Public inventory summary
Architecture
Web
API
Data
A Vite front end proxies to an Express API so the auth cookie stays first-party; Postgres enforces integrity. Select a component to trace its connections.
Key decisions
ADR-01Lock rows on approval
- Context
- Two admins approving requests at once could both see enough stock.
- Decision
- Approvals lock the request and inventory rows inside a transaction and re-check stock.
- Trade-offs
- Serialises concurrent approvals of the same item — exactly what we want.
ADR-02Conditional updates against double review
- Context
- A pending item could be reviewed twice from two tabs.
- Decision
- Updates include `where status = pending`, so the second review changes nothing.
- Trade-offs
- None worth mentioning.
Challenges
Dates shifting by a day
Problem. Postgres DATE values were parsed as midnight UTC and displayed as the previous day in Dhaka.
Solution. Override the driver’s DATE parser to keep dates as plain calendar dates.
Quality & delivery
Testing
Type-checked end to end; manual test plans from the SRS. Automated tests are the next step.
Deployment
Docker Compose runs Postgres with a health check; idempotent schema and seed scripts give a one-command setup.
Outcome
- Tables
- 7
- 6 enum types, CHECK constraints
- API handlers
- 32
- 7 route modules
- Pages
- 20
- Public, user and admin
Version 2 runs end to end locally with seeded demo data. Version 1’s source is public as a learning milestone.
Lessons
- Rebuilding your own project is the fastest way to see how much you have learned.
Building something similar?
Tell me what you’re working on — I reply to every serious message.
Related work
Reunion Registration System
Full-stack registration for an SSC batch reunion: a Bengali form that mirrors the printed one, sequential coupons allocated in a transaction, one-click A4 PDFs generated in the browser and a searchable admin table with CSV export.
- JavaScript
- React
- Tailwind CSS
- Vite
- Node.js
- Express
- +1