Production Guide Church Apps BibleBridge API Reference Integrity Engine

How to Build a Church App Backend

A modern church app is infrastructure — sermons, events, and giving built on deterministic Scripture integrity and stable canonical data.

Most Bible APIs deliver text.
BibleBridge delivers canonical infrastructure your database can depend on.

What you’ll build in this guide

A step-by-step architecture that scales from one church to a multi-tenant platform—plus exactly how to implement Scripture features using BibleBridge endpoints: /api/resolve, /api/scripture, /api/search, /api/votd, and /api/batch.

Sermons Events Giving Announcements Notes & bookmarks Push notifications Offline mode Full-text search Multi-tenant Cross-translation compare Batch retrieval

Sizing reality check

Scripture units
31,000+ verses
Hot reads
Highly cacheable
Sunday WiFi
Often unreliable
Goal: ship an MVP fast, but design your data model so you don’t rebuild later.

If you are implementing this in production, follow this sequence:

The Three Layers of a Production Church App

To answer “how do I build a church app?” you need more than a feature list. You need a layered architecture.

Layer 1: Client App
Mobile-first (iOS/Android), offline storage, push notifications, fast UX during services.
React Native / Flutter SQLite / IndexedDB Service worker (web)
Layer 2: Church Backend
Accounts, churches, roles, sermons, events, giving, notifications, media storage, moderation.
PostgreSQL Object storage (S3) Redis + workers
Layer 3: Scripture Infrastructure
Deterministic Scripture reference integrity.
Validated canonical coordinates.
Structured retrieval, batch operations, search, and cross-translation alignment.
/api/resolve /api/scripture /api/search /api/votd /api/batch OSIS-compatible identifiers Stable 66-book canonical ordering

Reference architecture (request flow)

A stable flow prevents “canonical drift” and keeps offline usage reliable.

Canonical drift occurs when reference parsing rules change and stored strings no longer map deterministically to the same verse spans.

Diagram: Client → Backend → BibleBridge → Storage
Mobile/Web App
  +- Fetch church content (events, sermons, announcements)
  +- Submit human Scripture references (e.g., "1 Cor 13", "Rom 8:1-4, 28")
        |
        v
Church Backend (your app)
  +- Auth + roles (member/admin/pastor)
  +- Tenant isolation (church_id)
  +- Cache hot reads (Redis)
  +- Store canonical coordinates for notes/bookmarks/sermons
        |
        v
BibleBridge API
  +- /api/resolve   (reference string -> canonical spans + OSIS + book_id)
  +- /api/scripture (book_id/chapter/verse/range/endChapter/endVerse/version/compare)
  +- /api/batch     (bulk retrieval in one atomic request)
  +- /api/search    (full-text search by version; optional book_id scope)
  +- /api/votd      (daily verse for engagement + offline hydration)
        |
        v
PostgreSQL + Object Storage (S3) + Workers
Design rule: store Scripture references as canonical coordinates (book_id/chapter/verse ranges). Treat verse text as a retrievable view that can change by translation. This keeps your DB stable and your UI flexible.

Step 1: Define Your MVP Feature Set

Start with what churches expect by default. You can differentiate later—but you must cover the basics.

MVP checklist:
  • Home feed: announcements + featured sermon
  • Sermons: audio/video + notes + share links
  • Events: calendar + reminders
  • Giving: one-time + recurring (Stripe/provider)
  • Bible: verse lookup + reading experience
  • Engagement: notes, bookmarks, prayer requests (optional for MVP)
  • Offline: VOTD + current sermon passages cached locally
Practical tip: “sermons + events + giving + reliable Scripture” covers most adoption hurdles.

Step 2: Design for One Church or Many (Multi-Tenant)

If you plan to serve multiple churches, isolate data from day one. Otherwise you’ll rewrite permissions, analytics, and content later.

PostgreSQL / Prisma: Tenant Isolation
model Church {
  id        Int      @id @default(autoincrement())
  name      String
  slug      String   @unique
  createdAt DateTime @default(now())

  users     User[]
  sermons   Sermon[]
  events    Event[]
}

model User {
  id        Int      @id @default(autoincrement())
  churchId  Int
  email     String   @unique
  role      String   // "member" | "admin" | "pastor"
  createdAt DateTime @default(now())

  church    Church   @relation(fields: [churchId], references: [id])
}

model Event {
  id        Int      @id @default(autoincrement())
  churchId  Int
  title     String
  startsAt  DateTime
  endsAt    DateTime?
  location  String?
  details   String?
  createdAt DateTime @default(now())

  church    Church   @relation(fields: [churchId], references: [id])

  @@index([churchId, startsAt])
}
Common mistake: building “single church mode” without a tenant key. Adding it later is painful and risky.

Step 3: Build Sermons as a First-Class Feature

Sermons are anchor content. Make them searchable, shareable, and linked to Scripture deterministically.

Scripture linking rule: store canonical coordinates for sermon passages (book_id/chapter/verse ranges). Use BibleBridge to resolve pastor-entered text and to retrieve verse text for display.
Sermons: Media + Canonical Passage Coordinates
model Sermon {
  id          Int      @id @default(autoincrement())
  churchId    Int
  title       String
  speaker     String?
  preachedOn  DateTime
  mediaUrl    String   // S3 / CDN
  notesHtml   String?
  createdAt   DateTime @default(now())

  church      Church   @relation(fields: [churchId], references: [id])
  passages    SermonPassage[]

  @@index([churchId, preachedOn])
}

model SermonPassage {
  id         Int @id @default(autoincrement())
  sermonId   Int

  // Canonical coordinates (BibleBridge resolver -> spans -> stored here)
  book_id    Int     // 1-66
  chapter    Int
  verseStart Int
  verseEnd   Int?

  // Optional cross-chapter
  endChapter Int?
  endVerse   Int?

  sermon     Sermon @relation(fields: [sermonId], references: [id])

  @@index([book_id, chapter, verseStart, verseEnd])
}

Step 4: Enforce Deterministic Reference Integrity

Production systems cannot depend on loose string matching. Real-world input is messy and inconsistent: “1 Cor 13”, “I Cor. 13”, “First Corinthians 13”, or compound ranges like “Rom 8:1-4, 28; 12:1-2”. Before you store anything, normalize the reference into validated canonical coordinates.

Architectural axiom:
Canonical coordinates are your primary key.
Verse text is a projection.
What breaks without canonical coordinates?
Reading plans duplicate verses across translations
Sermon notes link to the wrong span after version changes
Analytics double-count compound ranges
Offline caches drift when string parsing changes
Rule: accept flexible input at the edge, then operate only on canonical coordinates internally. This keeps your database stable, your links deterministic, and your downstream features (search, analytics, compare) correct.
BibleBridge: /api/resolve (deterministic canonical normalization)
curl --get https://holybible.dev/api/resolve \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "reference=rom 8:1-4, 28; 12:1-2"
Store coordinates (not strings)
// The resolver returns:
// - references[].book.book_id  (canonical Book ID: 1-66)
// - references[].spans[]       (validated start/end coordinates)
// - osis_id                    (OSIS-compatible identifier for interoperability)
//
// Persist canonical coordinates for each span:
//   book_id, startChapter, startVerse, endChapter, endVerse
//
// Suggested storage shape:
//   { book_id: 45, startChapter: 8, startVerse: 1, endChapter: 8, endVerse: 4 }
//   { book_id: 45, startChapter: 8, startVerse: 28, endChapter: 8, endVerse: 28 }
//
// Optional: also store osis_id for share URLs and external interoperability.
Why this matters: canonical storage unlocks everything else—fast retrieval, offline caching, reading plans, passage sharing, analytics, and cross-translation comparison—without ever re-parsing strings.
Why your regex fails: compound segments, implicit chapter carry-forward, ordinals, abbreviations, punctuation variance, ambiguity handling, and canon-aware bounds checking cannot be safely handled with naive parsing.
Read the full architectural breakdown

Why Not Regex, Scraping, or Generic Text APIs?

Many Bible APIs provide verse retrieval by reference or internal content IDs. That solves text delivery. It does not solve canonical normalization.

The difference is architectural: BibleBridge is designed as canonical infrastructure, not just a text feed.
  • Regex-based parsing: Cannot safely handle compound segments, implicit chapter carry-forward, ordinals, punctuation variance, ambiguity handling, or canon-aware bounds checking.
  • Scraping websites: Introduces structural fragility, licensing ambiguity, and silent breaking changes.
  • Generic text APIs: Often require pre-formatted references or internal IDs and do not provide deterministic canonical normalization before retrieval.
  • Client-side alignment: Many APIs require manual cross-translation joins. BibleBridge performs server-side canonical alignment using stable Book IDs.

BibleBridge separates Scripture reference integrity from text retrieval.

Inputs are first normalized into validated, canon-aware coordinates, then retrieved deterministically.

This eliminates ambiguity, enforces structural correctness, and protects long-term schema stability.

Step 5: Retrieve Canonical Scripture with /api/scripture

Once canonical coordinates are stored (book_id, chapter, verse or range), retrieve Scripture text structurally using /api/scripture. This enforces canon-aware bounds, preserves cross-translation alignment, and eliminates scraping fragility.

Never store Scripture as raw strings. Store canonical coordinates, then resolve text deterministically at read time.

Fetch a single verse
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=19 \
  --data chapter=23 \
  --data verse=1 \
  --data version=KJV

Example Response: Psalm 23:1 (KJV)
{
  "status": "success",
  "type": "single_verse",
  "version": "KJV",
  "book": {
    "id": 19,
    "name": "Psalm"
  },
  "chapter": 23,
  "range": null,
  "results_count": 1,
  "data": {
    "verse": 1,
    "text": "A Psalm of David. The LORD is my shepherd; I shall not want."
  }
}

Mapping tip: use book.id, chapter, and data.verse to align directly with your stored canonical coordinates.

Fetch a verse range (within one chapter)
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=19 \
  --data chapter=23 \
  --data range=1-6 \
  --data version=KJV
Fetch a full chapter
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=19 \
  --data chapter=23 \
  --data version=KJV
Fetch a cross-chapter span (e.g., Genesis 1:1-2:3)
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=1 \
  --data chapter=1 \
  --data verse=1 \
  --data endChapter=2 \
  --data endVerse=3 \
  --data version=KJV
Production pattern: treat Scripture retrieval as a backend responsibility. Your client calls your backend; your backend calls BibleBridge and caches responses. This protects API keys, centralizes rate limiting, and enables consistent Redis caching for high-traffic passages (e.g., Psalm 23, John 3:16, sermon texts).

Step 6: Cross-Translation Comparison (Aligned)

BibleBridge supports server-side verse alignment across two translations using the compare parameter. This eliminates client-side joins and translation-specific indexing assumptions.

Compare translations (KJV vs ASV)
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=19 \
  --data chapter=23 \
  --data range=1-3 \
  --data version=KJV \
  --data compare=ASV
Great for: Bible study mode, sermon slides, discipleship content, or “compare translations” UX.

Step 7: Batch Retrieval (Sermon Pages & Reading Plans)

Sermons often reference multiple passages. Reading plans pull multiple chapters. Use POST /api/batch to retrieve many references in one atomic request with deterministic ordering.

Batch request (multiple canonical references)
curl -X POST https://holybible.dev/api/batch \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "version": "KJV",
        "references": [
          { "book_id": 19, "chapter": 23, "range": "1-6" },
          { "book_id": 45, "chapter": 8, "verse": 28 }
        ]
      }'
  • Atomic processing: if one reference fails validation, the request fails.
  • Deterministic ordering: returned in the same order as requested.
  • One request: counts as a single API request for quota accounting.

Step 8: High-Performance Caching Patterns

Scripture reads are cacheable. Your app should be fast even on weak Sunday networks. Cache resolver outputs and retrieval outputs separately.

8.1 Cache the resolver output

Many users will enter the same references repeatedly (e.g., “Psalm 23”). Cache resolved coordinates for a long TTL.

Redis: Resolver cache (server-side)
// Pseudocode
const getResolved = async (input) => {
  const key = `resolve:${hash(input.toLowerCase())}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const url = "https://holybible.dev/api/resolve";
  const res = await fetch(`${url}?reference=${encodeURIComponent(input)}`, {
    headers: { "Authorization": `Bearer ${process.env.BIBLEBRIDGE_KEY}` }
  });
  const json = await res.json();

  await redis.setex(key, 86400, JSON.stringify(json)); // 24h
  return json;
};

8.2 Cache scripture retrieval outputs

Cache the result of /api/scripture (or /api/batch) for common reads: sermon passages, reading plan, Psalm 23, John 3:16.

Redis: Scripture cache keying
// Key by version + canonical coordinates
// Examples:
// scripture:KJV:19:23:range:1-6
// scripture:KJV:1:1:1-2:3 (cross-chapter)
// scripture:KJV:19:23 (full chapter)

const cacheKeyFor = (params) => {
  // params: { version, book_id, chapter, verse, range, endChapter, endVerse, compare }
  // Keep compare separate since response schema differs.
};
Two-tier caching strategy:
  • Tier 1: resolve cache (input string → canonical spans)
  • Tier 2: scripture cache (canonical spans → verse text JSON)

8.3 Handle Sunday Traffic Spikes (Edge Caching)

Church apps have a unique traffic profile: minimal usage during the week, followed by concentrated spikes during live services. Scripture content is immutable per translation, making it ideal for edge caching.

Production pattern: Apply HTTP caching headers at your backend layer so CDN providers (Cloudflare, Fastly, etc.) can cache /api/scripture responses at the edge. On a busy Sunday, requests for the sermon passage should be served from the CDN without hitting your application server.
// Example backend header strategy
Cache-Control: public, max-age=3600, s-maxage=86400

// Pre-warm likely passages before service:
// - Sermon text
// - Reading plan passages
// - Verse of the Day

This layered approach (CDN → Redis → BibleBridge) ensures deterministic performance under burst load.

Step 9: Offline Continuity & Sanctuary Dead Zones

Offline mode should be intentional. Don’t aim for “the whole Bible offline” at first. Aim for “the service experience works offline.”

Offline baseline:
  • Prefetch Verse of the Day daily via /api/votd and store locally
  • Prefetch sermon passages via /api/scripture or /api/batch
  • Queue writes (notes/bookmarks) locally and sync later
VOTD hydration (server job or app launch)
curl https://holybible.dev/api/votd \
  -H "Authorization: Bearer YOUR_API_KEY"

9.1 Delta sync queue (offline writes)

Track offline note/bookmark changes in a queue table and sync them when connectivity returns.

Example schema: SyncQueue
model SyncQueue {
  id         Int      @id @default(autoincrement())
  userId     Int
  type       String   // "note.create" | "bookmark.add" | "prayer.create" | ...
  payload    Json
  createdAt  DateTime @default(now())
  sentAt     DateTime?
  status     String   @default("pending")

  @@index([userId, status, createdAt])
}

Search is where most church apps feel slow. Use BibleBridge full-text search and scope it to reduce noise.

BibleBridge: Full-text search
curl --get https://holybible.dev/api/search \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data search=love \
  --data version=KJV
App-level win: add scopes like:
  • “Search within this book” by adding book_id
  • “Search within today’s reading” by filtering results client-side after retrieval
  • “Search within sermon passages” by searching locally on cached verses

10.1 If you also want local search

Many apps combine hosted search with local search for cached sermon passages and offline experiences. Use a small local index (SQLite FTS) for cached passages only—not the entire corpus.

Step 11: Deployment & Licensing Compliance

Production apps must respect provenance and licensing. BibleBridge serves verified public-domain Scripture editions and stable canonical schemas to avoid takedowns and silent data drift.

Requirement BibleBridge Scraping
Attribution No attribution required (public-domain sources) Often mandatory / restricted
Commercial use Permitted Often prohibited
Schema stability Stable canonical IDs Brittle / dynamic
Maintenance API contract + OpenAPI Breaks when HTML changes
Rule of thumb: If you can’t confidently describe the license terms for your Bible text, don’t ship it.
Production strategy: Ship your MVP on verified public-domain Scripture with stable canonical identifiers. This lets you validate architecture, caching, and offline flows without licensing risk. Introduce licensed modern translations later, once your distribution model and legal posture are established.

End-to-End Example: Sermon Passage Flow

This is the clean production workflow you want:

  1. Pastor enters: “Rom 8:1-4, 28; 12:1-2”
  2. Your backend calls /api/resolve to normalize and validate
  3. You store canonical coordinates (book_id + spans)
  4. You render the passage by calling /api/scripture (or /api/batch)
  5. You cache results for Sunday reliability
  6. Optional: enable compare for study mode
Resolve then retrieve (conceptual server-side flow)
// 1) Resolve
curl --get https://holybible.dev/api/resolve \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data-urlencode "reference=rom 8:1-4, 28"

// 2) Store returned book_id and spans as canonical coordinates

// 3) Retrieve (example for Romans 8:1-4)
curl --get https://holybible.dev/api/scripture \
  -H "Authorization: Bearer YOUR_API_KEY" \
  --data book_id=45 \
  --data chapter=8 \
  --data range=1-4 \
  --data version=KJV
Next Steps for Your Engineering Team

Implementation Roadmap

  1. Add /api/resolve to sermon admin input.
    Normalize pastor-entered references into canonical coordinates before storing anything.
  2. Store canonical coordinates instead of raw strings.
    Persist book_id, startChapter, startVerse, endChapter, and endVerse in your database.
  3. Refactor verse rendering to call /api/scripture.
    Treat verse text as a retrievable view, not static stored content.
  4. Add Redis caching.
    Cache resolver outputs (input ? spans) and scripture outputs (canonical spans ? verse JSON).
  5. Enable CDN edge caching for Sunday spikes.
    Apply proper Cache-Control headers so sermon passages are served from the edge during peak traffic.

This is not a verse API. It is canonical data infrastructure for production systems.

Build on Stable Scripture Infrastructure

BibleBridge provides deterministic Scripture reference integrity, structured retrieval, batch operations, full-text search, and cross-translation alignment — ensuring your church app remains stable, consistent, and canonically correct as it scales.

Next step: implement /api/resolve, store canonical coordinates, then render with /api/scripture.