Prabhupāda Says — BFF (Backend for Frontend)
What this project is
A Go-based Backend for Frontend (BFF) API server for prabhupadasays.org. It is the sole data provider for the production Next.js frontend (and future mobile apps). The knowledgebase frontend has removed all direct Prisma, MongoDB, and S3 dependencies — everything flows through this BFF. It aggregates content from MongoDB, user data from PostgreSQL, search from Typesense, caching from DragonflyDB, and media from S3 into a unified API surface.
Tech Stack
- Go 1.23 — API server
- Chi v5 — lightweight HTTP router with middleware support
- Zap — structured logging (uber-go/zap)
- MongoDB — content database (books, verses, letters, essays, glossary, disciples, transcriptions)
- PostgreSQL — relational data (users, bookmarks, collections, mentions, URL shortener)
- Typesense — typo-tolerant full-text search engine (https://typesense.org)
- DragonflyDB — Redis-compatible cache (API responses, rate limiting, sessions)
- AWS S3 — media storage (avatars, images, audio files)
- FCM + APNs — push notifications for Android and iOS
- JWT — stateless authentication
Architecture
Note: The BFF is now the ONLY data path for the frontend. The knowledgebase has no Prisma bypass, no direct MongoDB connections, and no direct S3 access. All content is served exclusively through this API.
Project Structure
spsays-bff/
├── cmd/
│ ├── api/
│ │ └── main.go → Entry point (config, logger, server, graceful shutdown)
│ └── indexer/
│ └── main.go → CLI tool for indexing MongoDB content into Typesense
│ • --full drop all collections and reindex
│ • --only=verses,letters index specific collections only
├── internal/
│ ├── config/
│ │ └── config.go → Environment config (all services: Mongo, Postgres, Typesense, S3, etc.)
│ ├── server/
│ │ └── server.go → Chi router setup, global middleware, route registration
│ ├── handler/ → HTTP handlers (one file per domain)
│ │ ├── health.go → Health check endpoint
│ │ ├── search.go → Federated search handler (multi-collection, typo-tolerant)
│ │ ├── book.go → Book routes (SB, CC, KB, BG, generic verse & prose)
│ │ ├── letter.go → Letter routes (by year, recipient, location)
│ │ ├── essay.go → Essay routes
│ │ ├── glossary.go → Glossary routes (letters, terms)
│ │ ├── disciple.go → Disciple routes (filter, search, aggregations)
│ │ ├── journal.go → Journal routes
│ │ ├── magazine.go → Magazine routes
│ │ ├── other_document.go → Other-document routes
│ │ └── transcription.go → Transcription routes (by year, place, type, slug)
│ ├── service/ → Business logic layer (orchestrates repositories)
│ ├── repository/ → Data access layer (Mongo, Postgres, Typesense, S3, Dragonfly)
│ │ ├── mongo.go → MongoDB connection + database handle
│ │ ├── book_repo.go → Book / verse queries
│ │ ├── letter_repo.go → Letter queries
│ │ ├── essay_repo.go → Essay queries
│ │ ├── glossary_repo.go → Glossary queries
│ │ ├── disciple_repo.go → Disciple queries
│ │ ├── journal_repo.go → Journal queries
│ │ ├── magazine_repo.go → Magazine queries
│ │ ├── other_document_repo.go → Other-document queries
│ │ ├── transcription_repo.go → Transcription queries (with contentJson fallback)
│ │ └── typesense.go → Typesense client wrapper (search, indexing helpers)
│ ├── middleware/
│ │ ├── logger.go → Zap-based request logging (method, path, status, duration)
│ │ ├── auth.go → JWT Bearer token authentication + context user extraction
│ │ └── ratelimit.go → In-memory token bucket rate limiter (→ DragonflyDB in production)
│ └── model/
│ ├── content.go → Content models (Book, Verse, Letter, Essay, GlossaryEntry,
│ │ Disciple, Transcription, ContentBlock, Synonym)
│ └── user.go → User models (User, Bookmark, Collection, Mention, ShortURL)
├── pkg/ → Public packages (importable by other projects)
│ ├── response/
│ │ └── response.go → Standardized API response helpers (JSON, Error, Envelope, Meta)
│ ├── validator/
│ │ └── validator.go → Field validation (Required, MaxLength, Email, In, etc.)
│ ├── push/ → Push notification sender (FCM + APNs) — placeholder
│ └── shorturl/ → URL shortener logic — placeholder
├── migrations/ → PostgreSQL migrations (goose or golang-migrate)
├── go.mod → Module: github.com/prabhupadasays/spsays-bff
├── go.sum
├── Makefile → build, run, dev, test, clean, lint, tidy
├── .env.example → Template for all environment variables
└── .gitignore
API Routes
All routes are prefixed with /api/v1.
Health
| Method |
Route |
Description |
GET |
/health |
Server health check |
Books
| Method |
Route |
Description |
GET |
/api/v1/books |
List all books with full metadata (chapters, cantos, parts, volumes, etc.) |
GET |
/api/v1/books/sb |
SB book metadata |
GET |
/api/v1/books/sb/{canto} |
SB canto chapter listing |
GET |
/api/v1/books/sb/{canto}/{chapter} |
SB chapter verse index |
GET |
/api/v1/books/sb/{canto}/{chapter}/verses |
SB paginated verses |
GET |
/api/v1/books/sb/{canto}/{chapter}/{verse} |
SB single verse |
GET |
/api/v1/books/cc |
CC book metadata |
GET |
/api/v1/books/cc/{lila} |
CC līlā chapter listing |
GET |
/api/v1/books/cc/{lila}/{chapter} |
CC chapter verse index |
GET |
/api/v1/books/cc/{lila}/{chapter}/verses |
CC paginated verses |
GET |
/api/v1/books/cc/{lila}/{chapter}/{verse} |
CC single verse |
GET |
/api/v1/books/kb |
KB book metadata |
GET |
/api/v1/books/kb/{volume} |
KB volume chapter listing |
GET |
/api/v1/books/kb/{volume}/{slug} |
KB single chapter |
GET |
/api/v1/books/bg |
BG book metadata |
GET |
/api/v1/books/bg/{chapter} |
BG chapter verse index |
GET |
/api/v1/books/bg/{chapter}/verses |
BG paginated verses |
GET |
/api/v1/books/bg/{chapter}/{verse} |
BG single verse |
GET |
/api/v1/books/{code} |
Any other book metadata |
GET |
/api/v1/books/{code}/chapters |
Prose book chapter listing (no paragraphs) |
GET |
/api/v1/books/{code}/chapters/{slug} |
Prose book single chapter |
GET |
/api/v1/books/{code}/{chapter} |
Verse book chapter index |
GET |
/api/v1/books/{code}/{chapter}/verses |
Verse book paginated verses |
GET |
/api/v1/books/{code}/{chapter}/{verse} |
Verse book single verse |
Letters
| Method |
Route |
Description |
GET |
/api/v1/letters |
Total letter count |
GET |
/api/v1/letters/years |
Year → count aggregation |
GET |
/api/v1/letters/recipients |
Recipient → count aggregation |
GET |
/api/v1/letters/locations |
Location → count aggregation |
GET |
/api/v1/letters/year/{year} |
Letters for a year |
GET |
/api/v1/letters/recipient/{name} |
Letters for a recipient |
GET |
/api/v1/letters/location/{name} |
Letters from a location |
GET |
/api/v1/letters/{slug} |
Single letter |
Essays
| Method |
Route |
Description |
GET |
/api/v1/essays |
All essay summaries |
GET |
/api/v1/essays/{slug} |
Single essay |
Glossary
| Method |
Route |
Description |
GET |
/api/v1/glossary |
All glossary entries |
GET |
/api/v1/glossary/letters |
Letter → count mapping |
GET |
/api/v1/glossary/letter/{letter} |
Entries for a letter (with diacritical grouping) |
GET |
/api/v1/glossary/letter/{letter}/terms |
Term names only for sidebar |
GET |
/api/v1/glossary/term/{term} |
Single entry by term |
Disciples
| Method |
Route |
Description |
GET |
/api/v1/disciples |
Filtered list (?letter=&suffix=) |
GET |
/api/v1/disciples/count |
Total + filtered counts |
GET |
/api/v1/disciples/letters |
Letter → count aggregation |
GET |
/api/v1/disciples/suffixes |
Suffix → count aggregation |
GET |
/api/v1/disciples/search |
Search by name (?q=) |
Journals
| Method |
Route |
Description |
GET |
/api/v1/journals |
All journal metadata |
GET |
/api/v1/journals/{book} |
Single journal meta |
GET |
/api/v1/journals/{book}/chapters |
Journal chapters |
GET |
/api/v1/journals/{book}/chapters/{slug} |
Single chapter |
Magazines
| Method |
Route |
Description |
GET |
/api/v1/magazines |
All magazine metadata |
GET |
/api/v1/magazines/{book} |
Single magazine meta |
GET |
/api/v1/magazines/{book}/issues |
Magazine issues |
GET |
/api/v1/magazines/{book}/issues/{slug} |
Single issue |
Other Documents
| Method |
Route |
Description |
GET |
/api/v1/other-documents |
All document metadata |
GET |
/api/v1/other-documents/{book} |
Single document meta |
GET |
/api/v1/other-documents/{book}/chapters |
Document chapters |
GET |
/api/v1/other-documents/{book}/chapters/{slug} |
Single chapter |
Transcriptions
| Method |
Route |
Description |
GET |
/api/v1/transcriptions |
Total transcription count |
GET |
/api/v1/transcriptions/years |
Year → count aggregation |
GET |
/api/v1/transcriptions/places |
Place → count aggregation |
GET |
/api/v1/transcriptions/types |
Type distribution ({lectures, conversations}) |
GET |
/api/v1/transcriptions/year/{year} |
Transcriptions for a year (summaries) |
GET |
/api/v1/transcriptions/place/{place} |
Transcriptions for a place (summaries) |
GET |
/api/v1/transcriptions/type/{type} |
Transcriptions by type (lecture or conversation) |
GET |
/api/v1/transcriptions/adjacent/{slug} |
Prev/next within same year (?year=N) |
GET |
/api/v1/transcriptions/{slug} |
Single transcription (full content + audio URL) |
Search
| Method |
Route |
Description |
GET |
/api/v1/search?q=...&type=...&page=1&limit=20 |
Federated typo-tolerant search across all content |
Query params:
q — search query (required)
type — content type filter, comma-separated. Valid values:
all (default) — search all 9 collections
verses, prose, letters, transcriptions, glossary, essays, journals, magazines, other_documents
- Multiple:
verses,letters,transcriptions
page — page number (default 1)
limit — results per page (default 20, max 100)
Response:
Authenticated (JWT required) — planned
| Method |
Route |
Description |
POST |
/api/v1/auth/register |
User registration |
POST |
/api/v1/auth/login |
User login (returns JWT) |
POST |
/api/v1/auth/refresh |
Refresh JWT token |
GET |
/api/v1/users/me |
Get current user profile |
PUT |
/api/v1/users/me |
Update current user profile |
GET |
/api/v1/collections |
List user's collections |
POST |
/api/v1/collections |
Create a collection |
POST |
/api/v1/collections/:id/items |
Add item to collection |
DELETE |
/api/v1/collections/:id/items/:itemId |
Remove item from collection |
GET |
/api/v1/mentions |
List user's mentions |
POST |
/api/v1/mentions |
Create a mention |
POST |
/api/v1/s |
Create a short URL |
POST |
/api/v1/notifications/register |
Register device for push notifications |
Typesense Indexing
The BFF includes a CLI tool (cmd/indexer/main.go) for managing Typesense indexes.
Collections (9 total, ~35,500 documents)
| Collection |
MongoDB Source |
Documents |
Searchable Fields |
verses |
verses |
21,127 |
translation, purport, transliteration, reference (+ ASCII variants) |
prose |
prose_chapters |
627 |
title, content (+ ASCII variants) |
letters |
letters |
6,866 |
content, recipient_display (+ ASCII variants) |
transcriptions |
transcriptions |
3,787 |
content, name, reference (+ ASCII variants) |
glossary |
glossary |
2,749 |
term, definition (+ ASCII variants) |
essays |
essays |
23 |
title, content (+ ASCII variants) |
journals |
journal_chapters |
300 |
title, content (+ ASCII variants) |
magazines |
magazine_issues |
59 |
title, content (+ ASCII variants) |
other_documents |
other_document_chapters |
28 |
title, content (+ ASCII variants) |
ASCII Shadow Fields
For diacritic-insensitive search, every searchable text field has a _ascii shadow field with all IAST diacritics stripped (ā→a, ṛ→r, ṣ→s, ṭ→t, ñ→n, etc.). This allows queries like "Krsna" to match "Kṛṣṇa", "Yashodanandana" to match "Yaśodanandana".
Search Configuration
prefix=true — partial word matching (e.g., "sarv" matches "sarva")
num_typos=2 — up to 2 character substitutions allowed per word
- HTML stripping — all paragraph content has HTML tags removed before indexing
CLI Usage
Data Models
Content Models (MongoDB)
| Model |
Collection |
Description |
Book |
book_meta |
Book metadata (title, author, chapters, etc.) |
Verse |
verses |
Individual verses (BG, SB, CC, ISO, NOI, BS, MM, NBS) |
Letter |
letters |
6,800+ letters (1947–1977) |
Essay |
essays |
23 standalone essays |
GlossaryEntry |
glossary |
2,749 Sanskrit/Vedic terms |
Disciple |
disciples |
4,747 initiated disciples |
Transcription |
transcriptions |
3,787 lectures, conversations, philosophy discussions, SB dictations — with audio URLs |
User Models (PostgreSQL)
| Model |
Table |
Description |
User |
users |
User accounts (email, name, avatar, role) |
Bookmark |
bookmarks |
Saved content references with optional notes |
Collection |
collections |
User-created collections of bookmarks |
Mention |
mentions |
Content shared/tagged by users |
ShortURL |
short_urls |
Shortened URLs with click tracking |
API Response Format
All responses follow a consistent envelope:
Error responses:
Middleware Stack
| Middleware |
Description |
RequestID |
Generates unique request ID (chi built-in) |
RealIP |
Extracts real client IP from proxy headers |
Logger |
Zap-based structured request logging |
Recoverer |
Panic recovery (chi built-in) |
Compress |
Gzip compression (level 5) |
CORS |
Cross-origin resource sharing (frontend URL whitelisted) |
Auth |
JWT Bearer token validation (applied to protected routes) |
RateLimit |
Token bucket rate limiter (in-memory → DragonflyDB in production) |
Environment Variables
How to Run
Relationship to Other Projects
| Project |
Path |
Relationship |
| knowledgebase |
../knowledgebase/ |
Next.js frontend — depends entirely on this BFF for all data (books, letters, essays, glossary, disciples, journals, magazines, other documents, transcriptions). Has NO direct database (Prisma/MongoDB) or S3 connections. The BFF must be running for the frontend to function. |
| extractor |
../extractor/ |
Python/TS extraction scripts + raw data + seed scripts |
| spsays-bff |
./ (this project) |
Go BFF API server — single source of truth for all content served to clients |
Migration status (Prisma → BFF):
- ✅ Done — BFF API routes matching all former Prisma queries
- ✅ Done — Frontend migrated from Prisma to BFF API calls (Prisma, MongoDB driver, and S3 SDK removed from knowledgebase)
- ✅ Done — Typesense indexing + federated search endpoints (
cmd/indexer/main.go, /api/v1/search)
- Add PostgreSQL for user features (auth, bookmarks, collections, mentions)
- Add mobile app support (same API, push notifications)
Design Principles
- Single source of truth — the BFF is the sole data provider for all content consumed by the frontend and future mobile apps. No client should bypass the BFF to access MongoDB, S3, or any other backing service directly.
internal/ — private to this module (handlers, services, repositories, middleware, models, config)
pkg/ — public packages that could be imported by other projects (response helpers, validators, push sender)
- Repository pattern — data access isolated behind interfaces (easy to swap Mongo ↔ Postgres, mock for tests)
- Service layer — business logic between handlers and repositories (orchestration, validation, caching)
- Handler layer — thin HTTP handlers that parse requests, call services, return responses
- No ORM — use native drivers (
mongo-go-driver, pgx) for full control
- Explicit book routing — SB, CC, and KB have dedicated route groups to avoid URL ambiguity with generic
{code}/{chapter}/{verse} patterns
- Search gateway — search is provided by Typesense, indexed from MongoDB via the indexer CLI; the BFF acts as the search gateway with multi-collection federated queries.