Skip to content

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
Links
Product siteSource on GitHub
KinetiRx medicine billing screen with strip and loose dispensing and live GST totals

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.

Read the full article: how KinetiRx works →

KinetiRx POS with strip and loose dispensing and live GST invoice totalsKinetiRx medicine inventory with batch, expiry and rack locationKinetiRx patient records with visit history, dues and doctor assignmentKinetiRx due-khata credit ledger with outstanding dues and reminders

Expertise this project shows

Related articles

Official project links

Have something in mind?

Let’s build something useful.

Tell me about the idea, product, or workflow you’re working through.

Tap to say hello