Skip to main content

FastAPI β€” NHS Quickstart

⚑ Python APIs · OpenAPI docs · Pydantic types · SQL Server/Postgres/DuckDB
Why FastAPI for the NHS

A thin, secure layer to expose read-only views and simple write endpoints with excellent developer experience, built-in docs, and strong typing. Perfect for serving KPIs, powering front-ends, or exposing models.

Great for: Developer Β· Data Scientist (serve models) Β· BI Analyst (API over views) Β· Data Engineer (pipeline control).


βš™οΈ 10-minute install​

python -m venv .venv && source .venv/bin/activate    # Windows: .venv\Scripts\activate
pip install fastapi uvicorn[standard] sqlalchemy pyodbc python-dotenv pydantic-settings

Create a project:

nhs-kpi-api/
app.py
models.py
.env
requirements.txt # optional

.env (local only β€” never commit)

SQLSERVER_SERVER=YOURSERVER
SQLSERVER_DATABASE=NHS_Analytics

πŸš€ β€œHello NHS” API β€” read from a view​

app.py
from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel
from sqlalchemy import create_engine, text
from sqlalchemy.engine import Engine
from pydantic_settings import BaseSettings
import os, urllib.parse

class Settings(BaseSettings):
sqlserver_server: str = os.getenv("SQLSERVER_SERVER", "")
sqlserver_database: str = os.getenv("SQLSERVER_DATABASE", "")

settings = Settings()

def get_engine() -> Engine:
params = urllib.parse.quote_plus(
"DRIVER={ODBC Driver 18 for SQL Server};"
f"SERVER={settings.sqlserver_server};"
f"DATABASE={settings.sqlserver_database};"
"Trusted_Connection=Yes;Encrypt=Yes;TrustServerCertificate=Yes;"
)
return create_engine(f"mssql+pyodbc:///?odbc_connect={params}", pool_pre_ping=True, pool_size=5, max_overflow=5)

app = FastAPI(title="NHS KPI API", version="0.1.0")

class KPI(BaseModel):
practice_id: str
total_appointments: int
attendance_rate: float | None = None
median_wait_minutes: float | None = None

@app.get("/healthz")
def healthz():
return {"status": "ok"}

@app.get("/kpi", response_model=list[KPI], tags=["kpi"])
def get_kpi(month: str | None = Query(None, pattern=r"^\d{4}-\d{2}$"), limit: int = 50, offset: int = 0, engine: Engine = Depends(get_engine)):
sql = text("""
SELECT practice_id, total_appointments, attendance_rate, median_wait_minutes
FROM dbo.vw_PracticeKPI
/* Optional month filter if your view has a month column */
WHERE (:month IS NULL OR month = :month)
ORDER BY total_appointments DESC
OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY
""" )
with engine.connect() as c:
rows = c.execute(sql, {"month": month, "limit": limit, "offset": offset}).mappings().all()
return [KPI(**row) for row in rows]

Run locally:

uvicorn app:app --reload --port 8000

Browse docs at http://127.0.0.1:8000/docs.


πŸ” Security patterns​

Add a shared header for development; replace with Entra ID in production.

from fastapi import Header, HTTPException

API_KEY = os.getenv("API_KEY", "dev-key")

def require_api_key(x_api_key: str = Header(...)):
if x_api_key != API_KEY:
raise HTTPException(status_code=401, detail="Invalid API key")

@app.get("/secure/kpi", dependencies=[Depends(require_api_key)])
def secure_kpi(...):
return {"ok": True}

CORS (if serving a browser UI)

from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://intranet.example.nhs"],
allow_methods=["GET"],
allow_headers=["*"],
)

πŸ§ͺ Writes & bulk inserts (optional)​

from typing import List
from fastapi import Body

class Attendance(BaseModel):
practice_id: str
attended: bool
start_time: str # ISO 8601

@app.post("/attendance", tags=["write"])
def post_attendance(items: List[Attendance], engine: Engine = Depends(get_engine)):
sql = text("INSERT INTO dbo.attendance (practice_id, attended, start_time) VALUES (:practice_id, :attended, :start_time)")
with engine.begin() as conn:
conn.execution_options(autocommit=False)
conn.execute(sql, [i.model_dump() for i in items])
return {"inserted": len(items)}

For large batches to SQL Server, set fast_executemany=True on the create_engine dialect options and use executemany patterns.


🐳 Docker (production-like)​

Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PORT=8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Build and run:

docker build -t nhs-kpi-api .
docker run -p 8000:8000 --env-file .env nhs-kpi-api

Deploy to AWS App Runner or Azure Container Apps; keep the service private (VPC/VNet) and front with SSO/WAF as required.


πŸ”Ž Observability​

  • Structure logs as JSON and send to platform logs (CloudWatch, Log Analytics).
  • Add /healthz (ready) and optionally /livez (liveness).
  • Record query timings and result counts; alert on failures/timeouts.

πŸ›‘ IG & safety checklist​

  • Use read-only DB roles for GET endpoints; separate service accounts for writes.
  • Validate and whitelist query parameters; avoid raw string concatenation.
  • Apply small-number suppression before returning aggregates.
  • Store secrets in Key Vault / Secrets Manager; never commit .env.
  • Keep OpenAPI docs private in production or require auth.

πŸ“ Measuring impact​

  • Latency: p95 request time for /kpi.
  • Reliability: 5xx rate, uptime.
  • Security: % endpoints protected; secrets from store, not files.
  • Adoption: number of dashboards/services consuming the API.

πŸ”— See also​

See also: Python Β· SQL Β· Docker Β· AWS Β· Azure Β· React Β· Secrets & .env

What’s next?

You’ve completed the Learn β€” FastAPI stage. Keep momentum: