⚙️ J4H Backend Explainer

How the server side of this app is built

The J4H backend is a Python Flask application that serves pages, handles API requests, talks to a database, and integrates with external services. It runs on Heroku in production and is kept deliberately simple — one file, one server, one database.
🐍Python
⚗️Flask
🗄️PostgreSQL
🦄Gunicorn
☁️Heroku
🤖Claude AI
🏥FHIR
📧Flask-Mail
🐍
Python
The programming language the entire backend is written in
  • All server-side code lives in app.py, database.py, and fhir_service.py
  • Python 3.14 is used in production on Heroku
  • Dependencies are listed in requirements.txt and installed with pip
  • Environment variables (API keys, DB URL) are loaded with the python-dotenv library
  • Tests are written with pytest and run locally and in CI
requirements.txt — key dependencies
flask==3.0.0 anthropic==0.78.0 psycopg2-binary==2.9.9 fhirclient==4.1.0 gunicorn==21.2.0 Flask-Mail==0.9.1 flask-cors==4.0.0
Why Python? Python has excellent libraries for web (Flask), AI (Anthropic), healthcare (fhirclient), and databases. It reads clearly and is the dominant language in health informatics.
⚗️
Flask
The web framework — routes, templates, and API endpoints
  • Flask is a micro-framework — it provides routing and templating but nothing else by default
  • Every URL in the app maps to a Python function via @app.route()
  • Page routes return render_template() — serving HTML files from templates/
  • API routes return jsonify() — serving JSON data consumed by JavaScript
  • There are ~35 routes in app.py covering pages, diary APIs, FHIR, summaries, and email
  • Flask-CORS is enabled so the Android app can make cross-origin API requests
Example — a page route vs an API route
# Page route — serves HTML @app.route('/entries') def entries_page(): return render_template('entries.html') # API route — serves JSON @app.route('/api/entries', methods=['GET']) def get_entries(): entries = db.get_entries() return jsonify(entries)
Why Flask over Django? Django includes an ORM, admin panel, and auth system that J4H doesn't need. Flask lets us keep exactly what we use and nothing more.
🗄️
Database — SQLite & PostgreSQL
SQLite locally, PostgreSQL in production on Heroku
  • All database logic is in database.py — a custom Database class
  • Locally, the app uses SQLite (a file-based database, no server needed)
  • On Heroku, it uses PostgreSQL via the DATABASE_URL environment variable
  • The same database.py code works for both — it detects which one to use automatically
  • Tables: entries (diary), summaries, settings
  • Diary entry content and location are stored as encrypted ciphertext (from the browser)
  • No ORM (like SQLAlchemy) — raw SQL queries are used directly
Example — how database.py detects which DB to use
DATABASE_URL = os.getenv('DATABASE_URL') if DATABASE_URL: # Heroku PostgreSQL conn = psycopg2.connect(DATABASE_URL) else: # Local SQLite conn = sqlite3.connect('app.db')
Why not use an ORM? The schema is simple — three tables. Raw SQL is easier to read and debug than ORM-generated queries for a project this size.
🦄
Gunicorn
The production web server — sits in front of Flask
  • Flask's built-in server is for development only — it can only handle one request at a time
  • Gunicorn (Green Unicorn) is a production WSGI server that handles concurrent requests
  • Heroku starts the app using the Procfile: web: gunicorn app:app
  • This means: run gunicorn, find the Flask app object named app in app.py
  • Gunicorn manages worker processes and passes HTTP requests to Flask
Procfile
web: gunicorn app:app
Why Gunicorn? It is the standard WSGI server for Python on Heroku. It is simple to configure and handles the concurrency that Flask's dev server cannot.
☁️
Heroku
The cloud platform hosting the app
  • Heroku is a Platform as a Service (PaaS) — no server management needed
  • Deploying is a single command: git push heroku main
  • Heroku detects Python, installs requirements.txt, and starts Gunicorn automatically
  • The app runs on a Basic dyno ($7/month) — always on, no sleeping
  • PostgreSQL is provided by the Essential-0 add-on ($5/month)
  • Secret keys and API keys are stored as Config Vars (environment variables) in the Heroku dashboard
  • The app URL is https://j4h-be058543801b.herokuapp.com — also served at https://j4h.org via Cloudflare
Deploying to Heroku
# Deploy to production git push heroku main # View live logs heroku logs --tail # Set a config var heroku config:set ANTHROPIC_API_KEY=sk-ant-...
Why Heroku? Zero infrastructure configuration. Push code, it runs. PostgreSQL is one command away. For a class project that needs to be always accessible, it is hard to beat.
🤖
Anthropic Claude API
AI-generated medical summaries from diary entries
  • The /api/summary endpoint sends diary entries to Claude and returns a plain-language medical summary
  • Uses the anthropic Python library with the claude-haiku model
  • The API key is stored as a Heroku config var: ANTHROPIC_API_KEY
  • Summaries are saved in the summaries database table for reuse
  • Summaries can be emailed to the user via the /api/email-summary endpoint
  • A disclaimer is always included: J4H is a communication aid, not medical advice
Example — calling Claude to generate a summary
client = anthropic.Anthropic() message = client.messages.create( model="claude-haiku-20240307", max_tokens=1024, messages=[{ "role": "user", "content": prompt }] )
Why Claude? Claude is well-suited for summarizing health notes with nuance and appropriate caution. The API is straightforward and the haiku model keeps costs low.
🏥
FHIR & fhirclient
Reading patient health records from a FHIR server
  • FHIR (Fast Healthcare Interoperability Resources) is the standard for exchanging health data
  • J4H connects to the GTRI FHIR server to fetch patient vitals, conditions, and demographics
  • All FHIR logic is in fhir_service.py — a FHIRService class
  • The fhirclient Python library handles authentication and resource parsing
  • Vitals (blood pressure, weight, cholesterol) are fetched and displayed on the /vitals page
  • Patient conditions and medications are shown on the /health-record page
  • The selected patient ID is stored in the settings table
Example — fetching vitals from the FHIR server
url = (f"{self.base_url}/Observation" f"?patient={patient_id}" f"&category=vital-signs" f"&_count=200") response = requests.get(url, headers=self.headers) bundle = response.json()
Why FHIR? FHIR is the real-world standard for health data exchange in the US. Building against it teaches the same patterns used in actual EHR integrations.
📧
Flask-Mail
Sending emails — summaries and encrypted history exports
  • Flask-Mail is a Flask extension that adds email sending via SMTP
  • Configured to use Gmail SMTP (smtp.gmail.com, port 587, TLS)
  • SMTP credentials are stored as Heroku config vars: MAIL_USERNAME, MAIL_PASSWORD
  • Used in two places: emailing AI summaries (/api/email-summary) and exporting encrypted history (/api/send-history)
  • The encrypted history export attaches a .j4h file — the server only handles ciphertext, never plaintext entries
Example — sending an email with an attachment
msg = Message( subject='J4H — Your Encrypted Health History', recipients=[recipient_email], html=email_body ) msg.attach('j4h-history.j4h', 'text/plain', encrypted_data.encode('utf-8')) mail.send(msg)
Why Gmail SMTP? Free, reliable, and requires no additional infrastructure. An app password is used so the main account password is never exposed.

The big picture

A single app.py file contains all Flask routes. It talks to PostgreSQL via database.py, reads FHIR health records via fhir_service.py, and calls the Claude API for AI summaries. Gunicorn runs Flask in production. Heroku hosts everything. The backend never sees plaintext diary entries — only the encrypted ciphertext stored in the database.