Prabhupāda Says — BFF (Backend for Frontend)
What this project is
A Go-based Backend for Frontend (BFF) API server for prabhupadasays.org. It sits between the Next.js frontend (and future mobile apps) and the data layer — aggregating content from MongoDB, user data from PostgreSQL, search from Meilisearch, 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)
- Meilisearch — typo-tolerant full-text search across all content
- 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
Project Structure
spsays-bff/
├── cmd/
│ └── api/
│ └── main.go → Entry point (config, logger, server, graceful shutdown)
├── internal/
│ ├── config/
│ │ └── config.go → Environment config (all services: Mongo, Postgres, Meili, S3, etc.)
│ ├── server/
│ │ └── server.go → Chi router setup, global middleware, route registration
│ ├── handler/ → HTTP handlers (one file per domain)
│ │ ├── health.go → Health check endpoint
│ │ ├── 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, Meili, 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)
│ ├── 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 (lightweight summaries) |
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) |
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 |
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 |
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 |
Description |
| knowledgebase |
../knowledgebase/ |
Next.js frontend — consumes this BFF's API |
| extractor |
../extractor/ |
Python/TS extraction scripts + raw data + seed scripts |
| spsays-bff |
./ (this project) |
Go BFF API server |
The frontend currently fetches directly from MongoDB via Prisma. The migration plan:
- ✅ Done — BFF API routes matching current Prisma queries
- Add Meilisearch indexing + search endpoints
- Add PostgreSQL for user features (auth, bookmarks, collections, mentions)
- Switch frontend from Prisma to BFF API calls
- Add mobile app support (same API, push notifications)
Design Principles
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