Skip to content
Back to projects
Contract projectDec 2025 – Feb 2026

Telemedicine Platform Backend

End-to-end NestJS backend for a telemedicine product — consultations, messaging, payments, wallets, uploads, calls, and notifications under a versioned API surface.

NestJSTypeScriptPostgreSQLPrismaRedisSocket.IOAWS S3Razorpay

Context

Brought in as a contract backend engineer to build and extend the production API for a telemedicine platform serving patients, doctors, and admins. The product needed reliable auth across three roles, consultation lifecycle management, wallet-based billing, real-time messaging, and integrations for payments, video calls, and file uploads.

  • Owned modular backend delivery across auth, consultations, payments, wallets, messaging, and infrastructure modules.
  • Designed for production use with structured error handling, API documentation, and webhook-first payment reconciliation.
  • Client and product names are omitted; this case study focuses on engineering decisions and system design.

Architecture

The backend follows a modular NestJS layout with clear separation between feature modules, shared infrastructure, and cross-cutting concerns. All business APIs live under a global /api/v1 prefix, with health probes and documentation routed separately.

System architecture

Admin app

Doctor app

Patient app

HTTPS / WebSocket

NestJS API

/api/v1 · guards · validation · throttling

Auth

OTP · JWT

Consultations

lifecycle

Messages

REST + Socket.IO

Payments

Razorpay

Wallets

ledger

Upload

S3 presigned

Calls

video tokens

Notifications

FCM

Shared

reference data

PostgreSQL

Prisma · split schemas

Redis / KeyDB

cache · Socket.IO scale

External services

OTP · Razorpay · S3 · video

  • Feature modules: auth, admin, doctors, patient, consultations, messages, payments, wallets, upload, calls, notifications, billing, and shared reference data.
  • Infrastructure layer: Prisma database access, Redis/KeyDB caching, external service adapters (OTP, Razorpay, S3, video), and a Redis-backed Socket.IO adapter for horizontal scaling.
  • Split Prisma schema by domain — identity, auth, consultations, financial, and reference data — to keep models maintainable as the product grew.
  • Global validation, throttling, structured error codes, and Swagger/OpenAPI docs for client and admin teams.

Authentication & access control

Auth is role-aware from the first request. Patients and doctors authenticate via phone OTP; admins use email/password or OTP depending on the client surface. Sessions are JWT-based with short-lived access tokens and rotating refresh tokens.

  • Three actor types — admin, doctor, patient — enforced with JWT guards, role decorators, and route-level permission checks.
  • OTP flows include rate limiting, expiry, attempt caps, and role immutability so a patient OTP cannot be replayed as a doctor session.
  • Session service tracks active tokens; logout and account suspension invalidate sessions immediately.
  • DoctorActive guard blocks suspended doctors from protected routes even with a valid token.

Consultations & workflow

Consultations are the core domain object connecting patients, doctors, billing, messaging, and calls. Status transitions are explicit so downstream systems — wallets, notifications, and settlement — can react predictably.

  • Lifecycle: requested → accepted → active → ended → settled.
  • Consultation end triggers automatic settlement logic tied to wallet debits and credits.
  • Admin tooling supports manual settlement and operational oversight when automated flows need intervention.
  • Availability, reviews, and reference data (specializations, symptoms, languages) support booking and discovery flows.

Payments & wallets

Financial logic uses a ledger model rather than a mutable balance column. Wallet balance is derived from credited and debited transactions, which makes reconciliation and audit trails easier to reason about.

Payment & settlement flow
1

Wallet recharge request

Patient requests recharge with idempotency key to prevent duplicate orders on retries.

2

Razorpay order created

Backend creates a payment order and returns checkout details to the client.

3

Payment captured

User completes checkout via Razorpay. Webhook is the primary reconciliation path.

4

Webhook verified & wallet credited

Signature-checked event writes a ledger credit — balance is derived from transactions, not a mutable field.

5

Consultation ends

Lifecycle moves to ended, triggering settlement logic across patient, doctor, and platform wallets.

6

Settlement split

Debit patient wallet, credit doctor earning and platform commission. GST tracked as separate ledger entries.

Fallback paths: order status polling and admin verify-order when webhooks are delayed.

  • Patient wallet recharge via Razorpay orders with idempotency keys for safe client retries.
  • Webhook-primary reconciliation: signature-verified Razorpay events credit wallets on successful capture.
  • Fallback paths include order status polling and admin verify-order endpoints when webhooks are delayed.
  • Consultation settlement splits value across patient debit, doctor earning credit, platform commission, and GST components.
  • Separate wallet contexts for patient, doctor, and platform with payout request flows for doctor withdrawals.

Real-time & integrations

Messaging is real-time; calls and notifications use dedicated integration paths suited to their delivery model.

  • Socket.IO gateway for consultation messaging, scaled with a Redis adapter for multi-instance deployments.
  • REST endpoints for message history alongside the real-time channel.
  • Video/audio calls via third-party token generation — consultation-scoped call start, end, and token endpoints.
  • Push notifications through FCM device token registration.
  • AWS S3 presigned PUT for uploads and presigned GET for private documents with short expiry and owner/admin access checks.

Trade-offs & learnings

Building a telemedicine backend under contract meant optimizing for clarity and operability, not just feature count. A few decisions that paid off:

  • Ledger-based wallets made payment debugging and webhook retries safer than storing a single balance field.
  • Webhook-first payments with explicit fallbacks reduced duplicate-credit risk without blocking the happy path.
  • Splitting Prisma schemas and Nest modules early kept auth, financial, and consultation logic from becoming a monolith.
  • OpenAPI generation gave frontend teams a stable contract while the backend evolved quickly.