Public-facing Go microservice gateway that provides frontend applications with drug information by querying the internal cash-drugs API. Handles API key authentication, per-key rate limiting, CORS origin locking, NDC normalization, and data transformation.
- NDC Lookup — Look up drugs by National Drug Code with automatic format normalization (5-4, 4-4, 5-3) and fallback padding
- Drug Class Lookup — Look up therapeutic/pharmacological classes by drug name (generic or brand, with brand fallback)
- Drug Names Listing — Paginated, filterable list of ~104K drug names (generic/brand) with substring search
- Drug Classes Listing — Paginated, filterable list of drug classes by type (EPC, MoA, PE, CS)
- Drugs by Class — List drugs belonging to a specific pharmacological class
- RxNorm Drug Search — Fuzzy drug name search via RxNorm approximate matching (top 5 candidates + spelling suggestions)
- RxNorm Drug Profile — Unified drug profile: RxCUI, generic equivalents, NDCs, brand names, related concepts
- RxNorm Granular Lookups — NDCs, generics, and related concepts by RxCUI
- Drug Autocomplete — Sub-3ms prefix search across 104K+ names via in-memory sorted index
- SPL Search & Detail — Search Structured Product Labels by drug name, retrieve parsed interaction sections
- Drug Info Card — Unified drug info (by name or NDC) with interactions, contraindications, warnings, adverse reactions
- Drug Interaction Checker — Submit 2–10 drugs and get cross-referenced interaction warnings from FDA SPL labels
- Circuit Breaker — Upstream resilience with automatic circuit breaker (10 fails → 30s cooldown) and stale-cache fallback
- API Key Authentication — Per-app API keys via
X-API-Keyheader, stored in Redis - Per-Key Rate Limiting — Sliding window rate limiter (Redis sorted sets) with configurable limits per key
- CORS Origin Locking — Per-key allowed origins list, or origin-free for server-to-server use
- Admin API — Create, list, get, deactivate, and rotate API keys via Bearer token auth
- Key Rotation — Rotate keys with a configurable grace period so old keys continue working during migration
- Prometheus Metrics — HTTP request metrics, cache hit/miss, auth/rate-limit rejections, Redis health, container system metrics (Linux)
- OpenAPI/Swagger — Interactive API docs at
/swagger/
# Set your admin secret
export ADMIN_SECRET=your-secret-here
# Start drug-gate + Redis
docker-compose upThe API is available at http://localhost:8081.
# Build
make build
# Run (requires Redis on localhost:6379)
REDIS_URL=localhost:6379 ADMIN_SECRET=your-secret make run| Method | Path | Description |
|---|---|---|
| GET | /health |
Health check with version |
| GET | /version |
Build version, git commit, branch, Go version |
| GET | /metrics |
Prometheus metrics endpoint |
| GET | /swagger/* |
Swagger UI |
| GET | /openapi.json |
OpenAPI spec |
| Method | Path | Description |
|---|---|---|
| GET | /v1/drugs/ndc/{ndc} |
Look up drug by NDC code |
| GET | /v1/drugs/class?name={name} |
Look up drug class by generic or brand name |
| GET | /v1/drugs/names |
Paginated drug names (filter: q, type, page, limit) |
| GET | /v1/drugs/classes |
Paginated drug classes (filter: type, page, limit) |
| GET | /v1/drugs/classes/drugs?class={name} |
Drugs in a pharmacological class |
| GET | /v1/drugs/autocomplete?q={prefix} |
Drug name typeahead (min 2 chars, max 50 results) |
| GET | /v1/drugs/spls?name={name} |
Search SPL documents by drug name |
| GET | /v1/drugs/spls/{setid} |
SPL detail with parsed interaction sections |
| GET | /v1/drugs/info?name={name} |
Drug info card (also accepts ?ndc={ndc}) |
| POST | /v1/drugs/interactions |
Check interactions between 2–10 drugs |
| GET | /v1/drugs/rxnorm/search?name={name} |
RxNorm fuzzy drug search (top 5 + suggestions) |
| GET | /v1/drugs/rxnorm/profile?name={name} |
Unified drug profile (RxCUI, generics, NDCs, related) |
| GET | /v1/drugs/rxnorm/{rxcui}/ndcs |
NDC codes for an RxCUI |
| GET | /v1/drugs/rxnorm/{rxcui}/generics |
Generic equivalents for an RxCUI |
| GET | /v1/drugs/rxnorm/{rxcui}/related |
Related concepts grouped by type |
| Method | Path | Description |
|---|---|---|
| POST | /admin/keys |
Create a new API key |
| GET | /admin/keys |
List all API keys |
| GET | /admin/keys/{key} |
Get a single API key |
| DELETE | /admin/keys/{key} |
Deactivate an API key |
| POST | /admin/keys/{key}/rotate |
Rotate a key with grace period |
| DELETE | /admin/cache |
Clear Redis cache (all or by prefix) |
curl -X POST http://localhost:8081/admin/keys \
-H "Authorization: Bearer $ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{"app_name": "my-frontend", "origins": ["https://myapp.com"], "rate_limit": 250}'curl http://localhost:8081/v1/drugs/ndc/00069-3150 \
-H "X-API-Key: pk_your_key_here"Response:
{
"ndc": "00069-3150",
"name": "Lipitor",
"generic_name": "atorvastatin calcium",
"classes": ["HMG-CoA Reductase Inhibitor"]
}curl -X POST http://localhost:8081/admin/keys/pk_old_key/rotate \
-H "Authorization: Bearer $ADMIN_SECRET" \
-H "Content-Type: application/json" \
-d '{"grace_period": "24h"}'| Environment Variable | Default | Description |
|---|---|---|
LISTEN_ADDR |
:8081 |
Server listen address |
CASHDRUGS_URL |
http://localhost:8083 |
Upstream cash-drugs API URL |
REDIS_URL |
redis:6379 |
Redis connection address |
ADMIN_SECRET |
(none) | Bearer token for admin endpoints |
CACHE_TTL |
60m |
Base cache TTL for drug data. RxNorm TTLs scale proportionally (24x search, 168x lookups) |
SYSTEM_METRICS_INTERVAL |
15s |
System metrics collection interval (Linux only) |
LANDING_URL |
(none) | When set, GET / redirects (302) to this URL. See Landing Page Redirect |
graph LR
Client([Frontend Client])
DG[drug-gate<br/>:8081]
Redis[(Redis)]
CD[cash-drugs<br/>:8083]
FDA[(FDA/DailyMed/RxNorm<br/>cached data)]
Client -->|X-API-Key| DG
DG -->|API keys<br/>rate limits| Redis
DG -->|data cache| Redis
DG -->|/api/cache/*| CD
CD --- FDA
graph LR
Req([Request]) --> RID[RequestID]
RID --> Log[RequestLogger]
Log --> Met[MetricsMiddleware]
Met --> Auth[APIKeyAuth]
Auth -->|valid key| CORS[PerKeyCORS]
Auth -->|missing/invalid| R401[401 Unauthorized]
CORS --> RL[RateLimit]
RL -->|under limit| Handler[DrugHandler]
RL -->|over limit| R429[429 Too Many Requests]
Handler --> Upstream[cash-drugs]
graph LR
Admin([Admin]) -->|Bearer token| AA[AdminAuth]
AA -->|valid| AH[AdminHandler]
AA -->|invalid| R401[401 Unauthorized]
AH --> Redis[(Redis)]
AH -->|CRUD| Keys[API Keys]
graph TD
CMD[cmd/server/main.go] --> H[handler/]
CMD --> MW[middleware/]
CMD --> AK[apikey/]
CMD --> RL[ratelimit/]
CMD --> MET[metrics/]
H --> |drug lookup| CL[client/]
H --> |NDC parsing| NDC[ndc/]
H --> |response types| M[model/]
H --> |admin CRUD| AK
H --> |listings| SVC[service/]
H --> |class parsing| PH[pharma/]
H --> |SPL data| SPLSVC[spl/service]
SVC --> CL
SPLSVC --> CL
SVC --> Redis
MW --> |auth| AK
MW --> |rate limit| RL
MW --> |metrics| MET
AK --> Redis[(Redis)]
RL --> Redis
CL --> CD[cash-drugs]
When you deploy drug-gate, you can optionally redirect GET / to an external landing page. This is controlled by the LANDING_URL environment variable.
If you're self-hosting, point it to your own page or leave it unset:
# Redirect root to your own landing page
LANDING_URL=https://your-domain.com/drug-gate
# Or disable the redirect entirely (default)
# Just don't set LANDING_URL — GET / returns no route (404)In Docker Compose:
services:
drug-gate:
image: dockerhub.calebdunn.tech/finish06/drug-gate:latest
environment:
- LANDING_URL=https://your-landing-page.com # or omit to disableWhen LANDING_URL is not set, no redirect is registered — the API behaves exactly as if the feature doesn't exist. This is the default for anyone cloning the repo.
make test-unit # Run unit tests
make test-coverage # Run tests with coverage report
make lint # golangci-lint
make vet # go vet
make build # Build binary to bin/server
# Integration tests (requires running Redis)
make test-integration
# E2E tests (spins up full stack via docker-compose)
make test-e2e
# k6 performance tests (against staging)
make k6-smoke # Smoke test (36 checks)
make k6-load # Load test
make k6-all # All k6 scenariosMIT