Case study · Pharmacy and clinic management
KinetiRx: Pharmacy and clinic management
Pharmacy billing, medicine stock with batches and expiry, patients, dues and OPD scheduling on a database the owner controls.
- My role
- Lead developer.
- Licence
- Self-hosted, free

Context
KinetiRx runs an independent pharmacy or small clinic: point-of-sale billing, medicine inventory, patient records, OPD scheduling, a credit ledger, daily cash-drawer reconciliation, expenses and staff accounts, on a PostgreSQL database the owner controls.
The project connects several jobs that are often treated as separate tools. A medicine sale changes stock; a patient purchase may leave a due balance; the cash drawer must still reconcile at the end of the day.
Problem
Small pharmacies keep billing, stock, patients and customer dues in a notebook, a spreadsheet and sometimes a hosted register, with sales and patient data sitting on a vendor's servers.
Users
Pharmacists, counter staff, clinic doctors and the owner or director.
Operational workflow
Medicines are dispensed by strip or loose unit with GST invoicing, stock is tracked per batch with expiry alerts, and the day closes with a cash-drawer reconciliation across cash, UPI and card.
Staff can move from a product lookup to a batch-aware sale, print the invoice, then review the same transaction in sales and drawer views. Patient records and OPD scheduling sit alongside the pharmacy workflow where a small clinic needs both.
Engineering challenge
A medicine is not a single undifferentiated stock number. The batch, expiry date, rack and dispensing unit affect what a counter worker can sell. The interface must surface those details without slowing down a queue.
Architecture
A React single-page application served by nginx, which reverse-proxies the API to a Go and Gin backend over the internal compose network. The browser sees one origin. The backend is the sole source of truth for authorisation: every route except health check and login validates a JWT and the employee's role and permissions on the server.
Data model decisions
Medicine records keep batch and expiry information alongside quantity and location. Sales, dues and drawer entries are related operational records, so a day's cash position can be explained from transactions instead of guessed from a total field.
Backend
Live Sync uses server-sent events so a second counter sees new sales, stock changes and register changes without a refresh. Purchase-bill OCR uses an optional Gemini integration, and the application degrades to an offline fallback when no API key is configured.
Frontend
The React and TypeScript interface gives counter staff a fast POS, while inventory screens expose batch, expiry and rack information. The dashboard brings sales, stock valuation and cash movement into one place for the owner.
Security
Passwords are bcrypt-hashed and nothing sensitive is logged. PostgreSQL is never published to the host in the supported deployment paths. The first-run account creation endpoint succeeds once and is rejected permanently as soon as any employee exists. A master security PIN, separate from login passwords, gates the system reset with a five-attempt lockout.
Infrastructure
Docker Compose or a one-click CasaOS and ZimaOS install. An optional MCP server lets an MCP-aware assistant operate a running instance through tool calls, kept behind a compose profile because it speaks over stdio and not a network port.
Reliability considerations
A change at one counter needs to appear at another without a page refresh; server-sent events keep those views current. The optional OCR integration cannot be a prerequisite for ordinary billing, so manual entry remains available when an API key or network is absent.
Difficult tradeoffs
Running on the owner's server gives control over records and deployment, while making local backups and upgrades an operational responsibility. Keeping OCR optional makes the base workflow less fragile, though it leaves manual purchase entry in the product.
What I personally worked on
Backend, frontend, packaging and the MCP server.
Lessons learned
In pharmacy software, inventory accuracy depends on modelling the unit and batch staff actually dispense. A generic product quantity misses the information a real counter needs.
Technologies
- Go
- Gin
- PostgreSQL
- React
- Vite
- TypeScript
- Docker
- nginx
Screenshots
Captured from running instances.
KinetiRx: frequently asked questions
- What is KinetiRx?
- KinetiRx runs an independent pharmacy or small clinic: POS billing, medicine inventory with batches and expiry, patient records, OPD scheduling, a credit ledger, cash-drawer reconciliation, expenses and staff accounts.
- Does it track medicine expiry?
- Yes. Stock is tracked per batch with expiry alerts, and records also keep rack location and the dispensing unit, strip or loose.
- Where is the data stored?
- In a PostgreSQL database on a server the owner controls. PostgreSQL is never published to the host in the supported deployments.
- Is it free?
- It is self-hosted and free to run. Backups and upgrades are the operator's responsibility.
- Does it need an AI service?
- No. Purchase-bill OCR uses an optional Gemini integration and manual entry always works without an API key.




Expertise this project shows
Related articles
- Pharmacy management software with batch and expiry tracking, billing and OPD: KinetiRx
- Pharmacy management software: a checklist for batch, expiry and billing
- What is forward deployed engineering? The job, the skills and how it differs from support
- WebSockets or server-sent events? Start with the direction of the conversation