Skip to content

Latest commit

 

History

137 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BibleQL

CI codecov Donate using Liberapay

A GraphQL API for querying Bible verses and passages across multiple translations. Supports localized book names so you can query in English ("John 3:16"), Spanish ("Juan 3:16"), and dozens of other languages.

Features

  • 40+ Bible translations in 30+ languages — public domain / freely licensed from open-bibles, plus extras from Bible List. Query translations or languages for the current figures.
  • Flexible passage lookup — single verses, ranges, multi-ranges (e.g., "Matthew 25:31-33,46")
  • Localized book names — query using book names in the translation's language (e.g., "Mateo 28:18-20" for Spanish)
  • Full-text search across verses
  • Semantic search — find verses by meaning using AI embeddings (pgvector + RubyLLM), currently available for spa-rv1909
  • Bible concordance — exhaustive word lookup with canonical ordering, per-book distribution, and keyword-in-context snippets
  • Verse of the Day — curated daily verse for any translation and date
  • Language discovery — list all available languages with translation counts
  • Translation hierarchy — browse books, chapters, and verse counts per translation
  • Bible index — full structural overview of any translation
  • API Key authentication with environment-aware prefixes (bql_live_ / bql_test_)
  • Rate limiting — 100 req/min per IP, 1,000 req/day per API key
  • Interactive Playground at /playground for exploring the API
  • Admin panel at /admin for managing API keys and requests

Documentation

Full API documentation, in English and Spanish: docs.bibleql.org

Guides, a quickstart, authentication, and a GraphQL reference generated from the schema itself. The site lives in docs/website and is deployed by .github/workflows/docs.yml — see CONTRIBUTING.md to run it locally.

Playground

The interactive GraphQL playground is available at /playground and lets you explore the API with example queries and a headers panel for authentication.

BibleQL Playground

Tech Stack

  • Ruby 4.0 / Rails 8.1
  • PostgreSQL
  • graphql-ruby
  • bible_parser — parses USFX/OSIS/Zefania XML Bible files
  • bible_ref — parses Bible reference strings
  • open-bibles — public domain Bible translations (git submodule)
  • Bible List — searchable collection of 1,550+ XML Bibles, source of the additional translations in db/biblelist/
  • Holy-Bible-XML-Format — 1,000+ XML Bibles across 200+ languages, downloaded individually as needed (not a submodule)
  • Docker + Kamal for deployment

Client Libraries

Language Package Source
Ruby bibleql-ruby (RubyGems) lporras/bibleql-ruby
Node.js / TypeScript bibleql-js (npm) lporras/bibleql-js

Prerequisites

  • Ruby 4.0+
  • PostgreSQL
  • Git (for submodules)

Setup

# Clone the repository (with submodules)
git clone --recurse-submodules https://github.com/lporras/bibleql.git
cd bibleql

# If you already cloned without submodules
git submodule update --init

# Install dependencies
bundle install

# Local environment variables (all optional — see below)
cp .env.example .env

# Create and migrate the database
bin/rails db:create db:migrate

# Import Bible translations (all ~45 open-bibles translations)
bundle exec rake bible:import

# Or import a single translation
bundle exec rake "bible:import_one[eng-web]"

# Build the concordance index (required for concordance queries)
bundle exec rake concordance:index

Environment variables

Local settings live in .env, which is gitignored and loaded by dotenv-rails. .env.example lists every variable with a comment explaining it. Nothing is required to run the API locally: each feature below is simply off until its variables are set.

Variables Needed for
DATABASE_URL A database other than the local bibleql_development
OPENAI_API_KEY The semanticSearch query
OFFLINE_R2_ACCOUNT_ID, OFFLINE_R2_ACCESS_KEY_ID, OFFLINE_R2_SECRET_ACCESS_KEY, OFFLINE_R2_BUCKET, OFFLINE_PUBLIC_BASE_URL Exporting offline translation packages (see below)
DOCS_URL Pointing documentation links at a local bin/docs server

Offline translation packages

Redistributable translations can be exported as gzipped SQLite files that clients download once and use offline (format and client contract). The files are uploaded to a Cloudflare R2 bucket. To set it up for development:

  1. In the Cloudflare dashboard, create an R2 bucket.
  2. In R2 → Manage API tokens, create an Object Read & Write token scoped to that bucket. Copy the Access Key ID and Secret Access Key (the secret is shown only once). Your Account ID is in Account Details on the R2 overview page.
  3. In the bucket's Settings, enable the Public Development URL (https://pub-<hash>.r2.dev).
  4. Put the five OFFLINE_* values in .env, then check them:
bundle exec rake offline:check_storage             # uploads a probe object and fetches it back publicly

# Only translations whose license allows redistribution may be exported (default: none)
DRY_RUN=1 bundle exec rake offline:flag_public_domain
bundle exec rake "offline:flag[spa-rv1909,true]"

# Exports run as background jobs, so start the worker (bin/dev starts it too)
bin/jobs
bundle exec rake "offline:export_one[spa-rv1909]"
bundle exec rake offline:list

Exports can also be started from the admin panel under Offline Packages, and every import re-exports downloadable translations automatically.

Additional translations

Beyond the open-bibles submodule, BibleQL can import XML Bibles from Bible List — a searchable collection of 1,550+ XML Bibles. Drop the downloaded files in db/biblelist/, describe them in config/biblelist_translations.yml, and import them with their own command:

bundle exec rake biblelist:list                    # show configured files and import status
bundle exec rake biblelist:import                  # import all of them
bundle exec rake "biblelist:import_one[eng-niv]"   # import a single one

These files use a different XML shape than the open-bibles formats (<bible><testament><book number="1">), handled by the bible_parser format plugin in lib/biblelist_format/. They identify books by ordinal number only, so their localized book names are copied from an already-imported translation of the same language (book_names_from in the YAML) — run rake bible:import first.

Note: most translations available through Bible List are copyrighted. Confirm you hold the necessary rights before exposing any of them through a public API.

Holy-Bible-XML-Format translations

A third source, Holy-Bible-XML-Format, is a 1,000+ file, 200+ language collection. It's not a git submodule: Render (and most git-based deploy pipelines) automatically fetch every submodule registered in .gitmodules on every deploy, which isn't workable for a source this large. Instead, download only the specific translations you want, one file at a time, into db/holy-bible-xml/ — that directory is gitignored, so nothing you put there is ever committed to this repo or fetched by CI/Render.

Browse the repo for the filename you want (they follow [LanguageName][Variant]Bible.xml, e.g. ArabicSVDBible.xml, SpanishNTVBible.xml), download it, then import it by the identifier derived from its filename (see docs/holy-bible-xml-identifiers.md for exactly how that derivation works — e.g. ArabicSVDBible.xml → ara-svd):

mkdir -p db/holy-bible-xml

curl -fsSL -o db/holy-bible-xml/ArabicSVDBible.xml \
  https://raw.githubusercontent.com/lporras/Holy-Bible-XML-Format/master/ArabicSVDBible.xml

bundle exec rake "holy_bible_xml:import_one[ara-svd]"

These files use the same XML shape as db/biblelist/'s, so they're parsed by the same BiblelistFormat::Parser. Unlike db/biblelist/, there's no hand-curated metadata file for 1,000+ translations — HolyBibleXmlFilenameParser derives each translation's identifier, language code, and abbreviation straight from its filename, falling back to a slugified language name for anything not in config/language_codes.yml. import_one skips (rather than re-imports) any identifier that's already in the database, so re-running it never creates duplicates. Book names are copied from whichever already-imported translation of the same language has the most of them, falling back to the canonical book name — run rake bible:import first.

bundle exec rake holy_bible_xml:import will import every *.xml file already sitting in db/holy-bible-xml/ (skipping any already-imported identifiers) if you've downloaded more than one — there's just no bulk-download step for the full 1,000+ file source, by design.

These tasks are meant to be run locally (or from any machine with the files downloaded) against whichever database you point DATABASE_URL/RAILS_ENV at, including production:

RAILS_ENV=production DATABASE_URL=<production-db-url> bundle exec rake "holy_bible_xml:import_one[ara-svd]"

HolyBibleXmlImporter builds the concordance index automatically as the last step of every import (same as every other importer), so freshly imported translations are immediately queryable via concordance/concordanceIndex. If you ever need to rebuild the index for a specific translation by hand — e.g. after editing config/language_codes.yml and re-importing, or to pick up a new text_search_config — target it directly by identifier instead of reindexing everything:

bundle exec rake "concordance:index[spa-ntv]"
bundle exec rake "concordance:index[spa-tla]"

bundle exec rake concordance:status   # confirm indexed_at / stemming per translation

Note: confirm you hold the necessary rights before exposing any imported translation through a public API.

Running Locally

bin/dev          # Rails server, Tailwind watcher, Solid Queue worker and docs site
# or just the API:
bin/rails server

Background jobs (offline package exports, emails) run on Solid Queue. With plain bin/rails server, start bin/jobs in another terminal to process them.

  • GraphQL endpoint: POST http://localhost:3000/graphql
  • Playground: http://localhost:3000/playground
  • GraphiQL IDE: http://localhost:3000/graphiql (development only)
  • Admin panel: http://localhost:3000/admin (development only)

Authentication

All POST /graphql requests require an API key via the Authorization header:

Authorization: Bearer bql_live_xxxxxxxxxxxxxxxx
  • Token prefixes: bql_live_ (production), bql_test_ (development/test)
  • Get an API key: Visit /api-keys/request/new to submit a request (admin approval required)
  • Manage keys via rake:
# Create a key
bundle exec rake "api_keys:create[name,email,environment]"

# List all keys
bundle exec rake api_keys:list

# Revoke a key
bundle exec rake "api_keys:revoke[prefix]"

GraphQL API

Quick example

{
  passage(translation: "eng-web", reference: "John 3:16") {
    reference
    text
    translationName
    verses {
      bookName
      chapter
      verse
      text
    }
  }
}

Response:

{
  "data": {
    "passage": {
      "reference": "John 3:16",
      "text": "For God so loved the world, that he gave his one and only Son, that whoever believes in him should not perish, but have eternal life.",
      "translationName": "World English Bible",
      "verses": [
        {
          "bookName": "John",
          "chapter": 3,
          "verse": 16,
          "text": "For God so loved the world, that he gave his one and only Son, that whoever believes in him should not perish, but have eternal life."
        }
      ]
    }
  }
}

Available queries

Query Description
translations List all available translations
translation(identifier) Get a single translation with nested books and chapters
books List all 66 canonical books
languages List all languages with translation counts
passage(translation, reference) Look up a passage (e.g., "John 3:16", "Mateo 28:18-20")
chapter(translation, book, chapter) Get all verses in a chapter
verse(translation, book, chapter, verse) Get a single verse
search(translation, query, limit) Case-insensitive substring search across verses (no stemming — use concordance for that)
semanticSearch(query, translation, limit) Search verses by semantic meaning using AI embeddings
randomVerse(translation, testament, books) Get a random verse with optional filters
verseOfTheDay(translation, date) Get the curated verse of the day
bibleIndex(translation) Get the structural hierarchy (books, chapters, verse counts)
concordance(translation, word, book, testament, first, after) Exhaustive, canonically-ordered concordance for a word, with per-book counts and KWIC context
concordanceIndex(translation, prefix, minOccurrences, first) Alphabetical word index with occurrence frequencies

Each query is documented with runnable GraphQL, cURL, Ruby and Node.js examples at docs.bibleql.org. See also docs/example_queries.md for raw request/response pairs for every query.

Reference Formats

The passage query supports these reference formats:

Format Example
Single verse "John 3:16"
Verse range "John 3:16-18"
Multiple ranges "Matthew 25:31-33,46"
Full chapter "Genesis 1"
Cross-chapter "Romans 12:1,3-4 & 13:2-4"
Localized names "Mateo 28:18-20", "Lucas 3:1-10"

Concordance

Unlike search, which returns the top-N most relevant verses, concordance returns every occurrence of a word in canonical order, along with aggregate counts.

Stemming availability varies by language. PostgreSQL ships dictionaries for about 30 languages; translations in other languages fall back to exact form matching. Check translation.hasStemming to know which behavior applies.

Run rake concordance:index after importing translations, or concordance queries will return an error prompting you to build the index.

Rate Limiting

The API is protected by rate limiting via Rack::Attack:

Scope Limit
GraphQL requests per IP 100/minute
GraphQL requests per API key 1,000/day
API key request form per IP 5/hour

Exceeded limits return a 429 Too Many Requests response with a Retry-After header.

Running Tests

bundle exec rspec

Linting

bin/rubocop

Deployment

BibleQL is deployed using Docker and Kamal. See config/deploy.yml and Dockerfile for details.

Production needs SOLID_QUEUE_IN_PUMA=true so the web process also runs the background-job worker, and, for offline packages, the five OFFLINE_* variables pointing at the production bucket (with a custom domain such as https://downloads.bibleql.org as OFFLINE_PUBLIC_BASE_URL).

The CI pipeline (GitHub Actions) runs security scans (Brakeman, Bundler Audit), linting (RuboCop), and tests (RSpec) on every PR.

Contributing

See CONTRIBUTING.md for guidelines on how to contribute to this project.

License

This project is open source. Bible translations included via open-bibles are Public Domain or Creative Commons licensed.

About

A GraphQL API for querying Bible verses and passages across multiple translations

Topics

Resources

Code of conduct

Contributing

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages