← All projects

PharmaStock Backend

B2B pharmaceutical stock management platform connecting Stockists (distributors) and Retailers (pharmacies). Stockists upload inventory files; the system processes them into a searchable catalog so retailers can instantly discover which distributors carry the medicines they need.

Open Source Private Server API Go 1.24+ Echo v5 PostgreSQL Linux VPS + Nginx
View GitHub Repository ↗ Discuss Project

What is PharmaStock?

A feature-first modular monolith built in Go to streamline B2B pharmaceutical stock discovery without microservice overhead.

The Challenge

Pharmacies frequently struggle to locate distributors with available stock for essential medicines. Distributors export inventory data in varying formats (.CSV, .PDF) with no centralized, searchable catalog.

The Solution

Distributors upload inventory files to the platform. An automated background worker parses, validates, and seeds a global medicine catalog in PostgreSQL, linking availability to specific stockists in real-time.

Core Actors

  • Stockist (Distributor): Uploads stock files, manages inventory.
  • Retailer (Pharmacy): Searches medicine catalog & discovers stockists.
  • Admin: Manages platform stockists, retailers, and system configuration.

Execution Services

  • API Server: Serves REST endpoints + server-rendered UI (HTMX).
  • Background Worker: Asynchronously polls and ingests file uploads.

How It Works

End-to-end flow from distributor inventory upload to pharmacy lookup.

flowchart LR
    subgraph Actors["Actors"]
        STK["Stockist"]
        RTL["Retailer"]
        ADM["Admin"]
    end

    subgraph System["PharmaStock Backend"]
        API["API Server - Echo v5"]
        UI["Browser UI - HTMX"]
        W["Worker Service"]
        DB[("PostgreSQL Database")]
    end

    STK -->|Upload CSV or PDF| API
    API -->|Store file and job| DB
    W -->|Poll pending jobs| DB
    W -->|Parse and seed catalog| DB

    RTL -->|Search medicines| UI
    ADM -->|Manage users| UI
    UI -->|Query catalog| API
    API -->|Return results| DB
            

Tech Stack & Technologies

Go 1.24+ Echo v5 PostgreSQL 13+ pgx v5 HTMX Alpine.js JWT & bcrypt Docker Compose golang-migrate Nginx Reverse Proxy Linux VPS
Layer Technology Implementation Details
Runtime & Language Go 1.24+ High-performance compiled binary for API server and background job worker.
Web Framework Echo v5 Lightweight HTTP routing, middleware pipeline, and graceful server shutdown.
Database PostgreSQL 13+ with pgx Raw SQL with high-performance connection pooling (min 2, max 10 connections).
Frontend UI Go HTML Templates + HTMX + Alpine.js Dynamic server-rendered UI without heavy JavaScript SPA frameworks.
Authentication JWT (HS256) & bcrypt Stateless authentication with fine-grained Role-Based Access Control (RBAC).
Catalog Search PostgreSQL pg_trgm GIN Index Sub-20ms fuzzy matching on medicine names using trigram similarity.
Deployment & Infra Linux VPS + Nginx Reverse Proxy Nginx for TLS termination, proxying to Systemd-managed Go binaries & Dockerized DB.

Modular Monolith Architecture

Every feature domain (Auth, Stockist, Retailer, Medicine, Inventory, Upload, Job) is structured as a self-contained module with clean dependency injection.

flowchart TB
    subgraph Server["Echo v5 API Server"]
        Router["Router Engine"]
        MW["Middleware Stack"]

        subgraph Modules["Feature Modules"]
            Auth["auth module"]
            Stockist["stockist module"]
            Retailer["retailer module"]
            Medicine["medicine module"]
            Inventory["inventory module"]
            Upload["upload module"]
            Job["job module"]
            UI["ui module"]
        end
    end

    subgraph Worker["Background Worker"]
        W["Job Processor"]
    end

    subgraph Storage["Storage Layer"]
        PG[("PostgreSQL Database")]
        FS["File Storage"]
    end

    Router --> MW
    MW --> Modules
    Upload --> FS
    Modules --> PG
    W --> PG
    W --> FS
            

Module Pattern Structure

Every domain directory maintains standard separation of concerns:

  • model.go — Pure Go domain entities (no JSON tags).
  • dto.go — Request/response validation schemas.
  • repository.go — pgx SQL query implementation.
  • service.go — Core business rules & domain logic.
  • handler.go & routes.go — HTTP handlers & routes.
  • module.go — Wire constructor for DI initialization.

Domain & Relational Model

  • Stockists: Distributors who own inventory lists & upload jobs.
  • Retailers: Pharmacies querying the central catalog.
  • Medicines: Deduplicated global catalog indexed with trigrams.
  • Inventories: Composite key join table (stockist_id, medicine_id).
  • Jobs: File upload processing state machine records.

Background Ingestion Worker

Heavy CSV and PDF inventory parsing runs asynchronously to prevent HTTP request timeouts and keep the UI responsive.

stateDiagram-v2
    [*] --> Pending
    Pending --> Processing : Worker claims job
    Processing --> Completed : File parsed and catalog seeded
    Processing --> Failed : Invalid format or parse error
    Processing --> Pending : Stale job reset timeout
    Completed --> [*]
    Failed --> [*]
            

Batch Ingestion Pipeline

When a stockist uploads an inventory file, the API saves the raw file to disk and inserts a job record with status pending. The background worker polls PostgreSQL every 10 seconds, picks up pending jobs, parses rows in memory, batch-inserts medicines using ON CONFLICT DO NOTHING, and links inventory records in bulk.

Fault Tolerance & Recovery

If a worker instance crashes mid-processing, jobs won't remain stuck indefinitely. The worker executes a ResetStaleJobs() routine on every cycle, resetting any job stuck in processing for more than 5 minutes back to pending for automatic retry.

Linux VPS Deployment & Reverse Proxy

Deployed on a dedicated Linux VPS with an Nginx reverse proxy managing SSL/TLS termination, request forwarding, and process management via Systemd.

flowchart TB
    Client["Client Browsers and API Clients"] -->|HTTPS Port 443| Nginx["Nginx Reverse Proxy"]

    subgraph VPS["Linux VPS Server Environment"]
        Nginx -->|Proxy Pass Port 8080| API["Go API Server - cmd/api"]
        Worker["Background Worker - cmd/worker"]

        API -->|Save or Read| FS["File Storage - uploads"]
        Worker -->|Parse Files| FS

        subgraph Docker["Docker Containers"]
            PG[("PostgreSQL 13 Database")]
        end

        API -->|Port 5432| PG
        Worker -->|Port 5432| PG
    end
            

Nginx Reverse Proxy Configuration

  • TLS Termination: SSL handling managed seamlessly at edge via Let's Encrypt.
  • Request Forwarding: Proxies requests to internal Go API server on port 8080 with X-Request-ID header propagation.
  • Performance: Enables Gzip response compression and static cache header policy.
  • App Protection: Combined with internal rate-limiting middleware (100 req / 5 min per IP).

Process Management & Reliability

  • Systemd Services: API server and Worker run as distinct Systemd background services configured to auto-restart on failure.
  • Docker Compose: Manages PostgreSQL container lifecycle and volumes.
  • Database Migrations: Automated via golang-migrate tool before service spin-up.
  • Graceful Shutdown: Both services listen for SIGINT/SIGTERM and allow 10s for active HTTP requests to complete before closing DB pools.

My Contributions

End-to-end backend engineering and system architecture designed and implemented by Dhrutinandan Swain.

Backend & Database Engineering

  • Architected the feature-first modular monolith structure in Go 1.24+ and Echo v5.
  • Implemented raw SQL repository pattern with pgx connection pooling and 6 database migrations.
  • Built fuzzy medicine search using PostgreSQL pg_trgm GIN trigram indexing.
  • Designed CSV/PDF parsing pipeline with idempotent batch insertion (ON CONFLICT DO NOTHING).
  • Implemented stateless JWT authentication with role-based middleware guards (admin, stockist, retailer).

UI, Infrastructure & DevOps

  • Developed browser UI using Go template clones, HTMX partial rendering, and Alpine.js.
  • Built asynchronous job worker with automatic stale-job detection and auto-recovery.
  • Deployed application stack on a Linux VPS behind an Nginx reverse proxy with TLS.
  • Configured Systemd service management, Docker Compose database setup, and graceful shutdown.
  • Authored full OpenAPI 3.0 specification for internal and public API documentation.

Key Architectural Decisions

Decision Rationale & Trade-Off
Modular Monolith Architecture Ensures strict domain boundaries while eliminating microservice network overhead, deployment complexity, and distributed tracing costs.
Raw SQL (pgx) over ORM Provides 100% control over query performance, eliminates hidden N+1 queries, and leverages native PostgreSQL features like trigram indexing.
DB Polling Worker over External Queue Avoids adding Redis or RabbitMQ infrastructure complexity for moderate file upload volumes, utilizing stale-job reset routines for recovery.
HTMX + Alpine.js over Heavy SPA Delivers fast, server-rendered interactivity without complex node build pipelines, heavy JavaScript bundles, or hydration mismatches.
Per-Page Template Clones Pre-compiles HTML template sets per page route, preventing block definition collisions in Go's html/template engine at runtime.
Idempotent Ingestion Operations Uses ON CONFLICT DO NOTHING so distributors can re-upload updated inventory files safely without corrupting catalog integrity.