- API base:
http://localhost:8000 - Book listing:
http://localhost:8000/bible/list_books - Search:
http://localhost:8000/bible/search?q=grace - Autocomplete:
http://localhost:8000/bible/suggest?q=gra - Random verse:
http://localhost:8000/bible/random_verse - Web UI:
http://localhost:8000/v2 - OpenSearch:
http://localhost:9200 - OpenSearch Dashboards:
http://localhost:5601 - API docs:
http://localhost:8000/docs
If search returns
lookup opensearch on 127.0.0.11:53: no such host, the API container can't resolve theopensearchservice name. Start all services viadocker composeso they share the same network.
OpenSearch 2.12+ requires an initial admin password. Set
OPENSEARCH_INITIAL_ADMIN_PASSWORDin your environment before runningdocker compose up.
- A raw high performance RESTful API written in Go
- King James Version Pure Cambridge Text
- No ads, No distractions, not ever.
- Hamburger navigation menu on every page (Books, Search, Docs, Donations, Settings, cross-link to v2/classic)
- Font settings: choose from Default, Blackletter (Gothic), Renaissance, or Classic Serif — persists via localStorage
- All Bible text preloaded into memory at startup for instant reads (zero OpenSearch latency for chapter/verse/random)
- Easy navigation
- Simple book listing and buttons choice
- Random Verse Generator
- All pages support json output
- provide argument:
?json=true
- provide argument:
- Forward chapter button (if applicable)
- Previous chapter button (if applicable)
- Books link button in Chapter selection
- Supports verse ranges
- Search feature with per-book chart visualization
- Example:
https://mintz5.duckdns.org/bible/search?q=heart
- Example:
- Predictive search bar on every page (autocomplete via OpenSearch)
- Complete King James Version
- Book navigation with clickable chapters
- Verse range support (e.g.,
/bible/ROMANS/5/1-5) - Full-text search powered by OpenSearch with per-book match chart
- Predictive autocomplete search bar on every page
- Hamburger navigation menu on every page (Books, Search, Docs, Settings, cross-link between classic/v2)
- Font settings with 4 choices: Default, Blackletter (Gothic), Renaissance, Classic Serif — saved in localStorage, persists across pages and sessions
- All ~31k verses preloaded into memory at startup — chapter reads, verse lookups, and random verse are served from cache with zero network latency
- OpenSearch — all Bible content reads, full-text search, autocomplete suggestions
- Redis — rate limiting, session storage
- No SQLite dependency. OpenSearch is the sole data source for Bible content.
The Donations menu item opens /donate, where supporters can choose a one-time or
monthly USD donation and are redirected to Stripe Checkout. Configure these values
outside source control before enabling payments:
STRIPE_SECRET_KEY=replace_with_rotated_secret
PUBLIC_BASE_URL=https://prsmusa.comSTRIPE_SECRET_KEY is server-only. Do not expose it in browser code, commit it to
the repository, or put it in a Vite environment variable. Monthly donations use
Stripe Billing; one-time donations request Stripe invoice creation. Configure Stripe
Tax and your account's donation receipt/tax settings in the Stripe Dashboard.
- 5 requests per second per IP address
- 1-minute blocking when limit exceeded
- Redis-based storage with automatic TTL cleanup
- Rate limit headers in all responses
- Admin endpoints for manual IP management
# Clone the repository
git clone https://github.com/r4wm/bible_api.git
cd bible_api
# Set the required OpenSearch admin password
export OPENSEARCH_INITIAL_ADMIN_PASSWORD=YourStrongPassword123!
# Start all services (OpenSearch, Redis, Bible API)
docker compose up -d
# Verify the service is running
curl "http://localhost:8000/health"The bible_api service waits for OpenSearch to be healthy before starting. However, Bible content endpoints (/bible/*) will return empty results until you index the data. On first run:
# Index the KJV data into OpenSearch (required before Bible endpoints work)
python3 scripts/index_kjv_to_opensearch.py --url http://localhost:9200
# Verify the index is working
curl "http://localhost:8000/bible/GENESIS/1?json=true"If autocomplete (/bible/suggest) returns poor results after upgrading, or if you've updated the indexer script, delete and rebuild the index:
curl -X DELETE http://localhost:9200/kjv_v2
python3 scripts/index_kjv_to_opensearch.py --url http://localhost:9200This is required whenever text_suggest indexing logic changes in scripts/index_kjv_to_opensearch.py.
GET /bible/v2/search accepts two optional controls:
match=any(default),match=all, ormatch=phrasecase_sensitive=true|false(defaultfalse)
case_sensitive=true queries the text.case_sensitive field. That field is populated only when a verse is indexed with the current mapping, so do not enable or rely on case-sensitive search against an existing index until it has been rebuilt.
Use a new versioned index rather than deleting the live index in place. The helper below refuses to overwrite an existing index:
# Create and populate a new index using the current mapping.
scripts/create_case_sensitive_index.sh \
--url http://localhost:9200 \
--index kjv_v2_case_sensitive_20260809
# Point the API at the new index, restart it, then verify the behavior.
export OPENSEARCH_INDEX=kjv_v2_case_sensitive_20260809
curl 'http://localhost:8000/bible/v2/search?q=God&case_sensitive=true'
curl 'http://localhost:8000/bible/v2/search?q=god&case_sensitive=true'Keep the previous index until the new one has passed API and UI smoke tests. The case-preserving analyzer uses normal token matching: match=phrase means an exact sequence of tokens, not byte-for-byte punctuation or whitespace matching.
# Ensure OpenSearch is running at localhost:9200
# Ensure Redis is running at localhost:6379
# Build
go build -o bible_api cmd/bible_api.go
# Run
./bible_apiThe public deployment at prsmusa.com splits the data plane from the API plane across two hosts joined by a WireGuard tunnel.
prsmusa.com (Linode) Home network
-------------------- ------------
Internet ──HTTPS──▶ nginx :443 ──path-route──▶ bible_api OpenSearch
│ :8000 ◀──WireGuard──▶ :9200
│ │ (wg0) (Pi)
├─/bible/ ─────────▶ │
├─/v2/ ─────────▶ │
├─/auth/ ─────────▶ │
├─/docs ─────────▶ │
└─/ ─────────▶ Other Docker apps :81
│
└─▶ Redis :6379 (local)
- nginx terminates TLS on
:443and proxies the bible_api paths (/bible/,/v2/,/auth/,/docs,/docs.json) to127.0.0.1:8000. Other paths fall through to unrelated Docker apps on:81. The site config lives at/etc/nginx/sites-enabled/prsmusa.nginx.conf. - bible_api runs natively (not in Docker) under systemd as the
r4wmuser.- Unit:
/etc/systemd/system/bible_api.service(a copy of bible_api.service) - Binary:
/usr/local/bin/bible_api(installed via install.sh) WorkingDirectory=/opt/bible_apiEnvironment="UI_DIST_DIR=/opt/bible_api/web/dist"— the React bundle lives at this path; the Go server reads it from disk at runtime viahttp.FileServer(see kjv/ui.go). Nogo:embed, so frontend-only changes do not require rebuilding the Go binary.Environment="OPENSEARCH_URL=http://<pi-wg-ip>:9200"— points across the tunnel to the Pi.- The unit has
After=wg-quick@wg0.serviceso the API doesn't start before the tunnel is up.
- Unit:
- Redis runs on the host at
localhost:6379(rate limiting + sessions). - The Linode does not have
npminstalled. The React bundle must be built elsewhere and shipped.
- Raspberry Pi running OpenSearch 2.x on
:9200. - Reachable from the Linode only via WireGuard (
wg0); not exposed publicly. - Indexed once with
scripts/index_kjv_to_opensearch.pyagainst the Pi's OpenSearch URL.
scripts/deploy.sh (driven by make deploy* targets) builds locally, rsyncs to r4wm@prsmusa.com, and restarts bible_api.service. The Go binary is cross-compiled CGO_ENABLED=0 GOOS=linux GOARCH=amd64 so the static ELF dodges glibc skew between dev box and Linode. The binary lands in /tmp/ first and is moved into /usr/local/bin/bible_api via sudo install -m 755 — atomic rename, no rsync-as-root.
make deploy # frontend + Go binary, then restart service
make deploy-ui # frontend only (web/dist/) — fast iteration on App.tsx
make deploy-backend # Go binary only, then restart
make deploy-dry # full deploy without the systemctl restartOverride the target host:
BIBLE_API_DEPLOY_HOST=r4wm@other.host make deploy
# or
./scripts/deploy.sh --host r4wm@other.hostThe script requires npm, go, rsync, ssh locally, and passwordless SSH + sudo on the target.
curl -I https://prsmusa.com/v2/ # 200 from the Go server
curl -s https://prsmusa.com/bible/random_verse?json=true | head # OpenSearch round-trip across WG
sudo systemctl status bible_api --no-pager # on the Linode
sudo journalctl -u bible_api -n 50 --no-pager # on the LinodeIf random_verse hangs or 5xx's, the WireGuard tunnel to the Pi is the first thing to check (sudo wg show, sudo systemctl status wg-quick@wg0).
The API includes built-in rate limiting to prevent abuse:
- Limit: 5 requests per second per IP
- Block Duration: 1 minute when exceeded
- Headers: Rate limit info in response headers
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 3
X-RateLimit-Reset: 1640995200
# Check IP status
GET /admin/rate-limit/{ip}
# Block IP manually
POST /admin/block-ip
{"ip": "192.168.1.100", "duration": "10m"}
# Unblock IP
DELETE /admin/unblock-ip/{ip}# Test random verse
curl "http://localhost:8000/bible/random_verse?json=true"
# Test search
curl "http://localhost:8000/bible/search?q=love&json=true"
# Test verse range
curl "http://localhost:8000/bible/JOHN/3/16-17?json=true"
# Test autocomplete
curl "http://localhost:8000/bible/suggest?q=grace"
# Test old English font mode (browser)
# http://localhost:8000/bible/GENESIS/1?olde=true
# Test rate limiting
./test_rate_limit.sh
./test_logging.shTo use the public version, visit the bible_api
GET /bible/list_books- List all Bible books (66 books in canonical order)GET /bible/{book}- List chapters in a bookGET /bible/{book}/{chapter}- Get all verses in a chapterGET /bible/{book}/{chapter}/{verse}- Get specific verseGET /bible/{book}/{chapter}/{start-end}- Get verse rangeGET /bible/search?q={query}- Full-text search with per-book chartGET /bible/suggest?q={prefix}- Autocomplete suggestions (n-gram completion)GET /bible/random_verse- Get random verse
GET /bible/v2/search?q={query}&match={any|all|phrase}&case_sensitive={true|false}- OpenSearch full-text search (JSON). Defaults:match=any,case_sensitive=false.GET /bible/v2/suggest?q={prefix}- Verse prefix suggestions (JSON)PUT /bible/v2/synonyms/{set}- Replace synonym set (JWT required)POST /bible/v2/synonyms/{set}- Append to synonym set (JWT required)DELETE /bible/v2/synonyms/{set}- Remove from synonym set (JWT required)GET /v2- Web UI (Google login + search/suggest)GET /docs- API documentation (HTML)GET /docs.json- API documentation (JSON)
POST /auth/google/token- Exchange Google ID token for app JWT + session cookieGET /auth/config- Auth configuration (Google client id)GET /auth/me- Get current session tokenPOST /auth/logout- Clear sessionPOST /admin/token- Mint token withX-Internal-Secret(internal use)
GET /admin/rate-limit/{ip}- Check IP rate limit statusPOST /admin/block-ip- Manually block an IPDELETE /admin/unblock-ip/{ip}- Unblock an IP
GET /health- Health check (bypasses rate limiting)
?json=true- Return JSON response instead of HTML?show_italics=true- Show italicized text in Bible verses (where applicable)?n={limit}- Limit number of results. Search defaults to 10000. Suggest defaults to 20 (max 50).
Font selection is handled via the Settings menu (hamburger menu → Settings) and saved in the browser's localStorage — no URL parameter needed.
Examples:
# Get verse in JSON format
curl "http://localhost:8000/bible/JOHN/3/16?json=true"
# Get verse with italics shown
curl "http://localhost:8000/bible/PSALMS/23/1?show_italics=true"
# Combine parameters
curl "http://localhost:8000/bible/ROMANS/8/28?json=true&show_italics=true"- Go 1.20+
- Redis server
- OpenSearch 2.x (required for all Bible content)
# Install dependencies
go mod tidy
# Build
go build -o bible_api cmd/bible_api.go
# Run (requires OpenSearch and Redis to be running)
./bible_apicd web
npm install
npm run devBuild static UI for /v2:
cd web
npm run buildThe Go server serves web/dist at /v2.
Docker builds the UI automatically during image build.
REDIS_ADDR=localhost:6379
REDIS_PASSWORD=
OPENSEARCH_URL=http://localhost:9200
OPENSEARCH_INDEX=kjv_v2
OPENSEARCH_SYNONYMS_SET=kjv_synonyms
JWT_SECRET=change_me
JWT_ISSUER=bible_api
JWT_AUDIENCE=bible_api_clients
JWT_TTL_SECONDS=3600
SESSION_TTL_SECONDS=3600
SESSION_COOKIE_NAME=bible_api_session
SESSION_COOKIE_SECURE=false
INTERNAL_TOKEN_SECRET=change_me
GOOGLE_CLIENT_ID=your-google-client-idLegacy English plain-text bible files are stored in assets/texts/, one translation per directory.
- Source: BibleSuper SourceForge - All Bibles (Plain Text) EN-English
kjv.txtis intentionally excluded from this set for now and should be skipped in import workflows.
asv.txt: American Standard Versionasvs.txt: American Standard Version (with Strong's numbers)bishops.txt: Bishops' Biblecoverdale.txt: Coverdale Biblegeneva.txt: Geneva Biblekjv_strongs.txt: King James Version (with Strong's numbers)net.txt: New English Translationtyndale.txt: Tyndale Bibleweb.txt: World English Bible
Each translation directory includes its own MD5SUM file.
cd assets/texts/asv
md5sum -c MD5SUMEach translation directory (example: assets/texts/asv/) contains:
<translation>.txtsource textREADME.mdwith source and notesCHANGELOG.mdfor change historyMD5SUMfor integrity verificationscripts/import_to_sqlite.pyfor SQLite ingestionscripts/import_to_opensearch.pyfor OpenSearch ingestionscripts/verify_import.pyfor checksum and import checks
bible_api/
├── cmd/
│ └── bible_api.go # Main application entry point
├── kjv/
│ ├── kjv.go # Core Bible API handlers (OpenSearch-backed)
│ ├── v2.go # OpenSearch helpers, v2 endpoints, suggest
│ ├── admin.go # Admin endpoints for rate limiting
│ ├── auth.go # Google OAuth + JWT auth
│ ├── ui.go # Static UI serving
│ └── templates.go # HTML templates with search bar
├── middleware/
│ └── rate_limiter.go # Redis-based rate limiting
├── assets/
│ └── texts/ # Legacy plain-text Bible translations
├── web/ # React UI (served at /v2)
├── scripts/
│ ├── deploy.sh # Build + rsync + systemctl restart on prsmusa.com
│ └── index_kjv_to_opensearch.py # Index Bible data into OpenSearch
├── docker-compose.yml # OpenSearch + Redis + Bible API
├── Dockerfile
├── start_with_redis.sh
├── test_rate_limit.sh
├── test_logging.sh
└── RATE_LIMITING.md
- Rate Limiting Guide - Detailed rate limiting documentation
- API Examples - Example API calls and responses
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
This project is open source. The King James Version text is in the public domain.