# Prabhupāda Says — BFF (Backend for Frontend) ## What this project is A Go-based Backend for Frontend (BFF) API server for [prabhupadasays.org](https://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 ``` ┌──────────────┐ ┌─────────────────────────────────────────┐ │ Next.js │────▶│ Go BFF API (:8080) │ │ (frontend) │ │ │ ├──────────────┤ │ cmd/api/main.go → server.go → handlers │ │ Mobile Apps │────▶│ │ │ (future) │ │ ┌──────────┐ ┌───────────────┐ │ └──────────────┘ │ │ Handlers │──│ Services │ │ │ └──────────┘ └───────┬───────┘ │ │ │ │ │ ┌─────────────────────┼──────────┐ │ │ ▼ ▼ ▼ ▼ ▼ │ │ Mongo Postgres Meili S3 Dragonfly│ │ (content)(users, (search)(media)(cache)│ │ mentions, │ │ URL short, │ │ relations) │ └─────────────────────────────────────────┘ ``` --- ## 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: ```json { "success": true, "data": { ... }, "meta": { "page": 1, "limit": 20, "total": 150, "total_pages": 8 } } ``` Error responses: ```json { "success": false, "error": { "code": 400, "message": "validation failed" } } ``` --- ## 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 ``` # Server APP_ENV=development # "development" | "production" PORT=8080 # MongoDB (content) MONGO_URI=mongodb://... # required MONGO_DB=spsays # PostgreSQL (users, relations) POSTGRES_URI=postgres://... # Meilisearch MEILISEARCH_HOST=http://localhost:7700 MEILISEARCH_API_KEY= # DragonflyDB / Redis REDIS_URI=redis://localhost:6379 # S3 S3_REGION= S3_BUCKET= S3_ACCESS_KEY= S3_SECRET_KEY= S3_ENDPOINT= # optional — for MinIO or S3-compatible services # Push Notifications FCM_CREDENTIALS_FILE= # path to Firebase service account JSON APNS_KEY_FILE= # path to APNs auth key (.p8) APNS_KEY_ID= APNS_TEAM_ID= APNS_BUNDLE_ID= # JWT JWT_SECRET= # required in production JWT_EXPIRATION=24h # Frontend (CORS) FRONTEND_URL=http://localhost:3000 ``` --- ## How to Run ```bash # Setup cp .env.example .env # then fill in values # Development (with live reload — requires air) make dev # Build + run make run # Build only make build # Run tests make test # Lint (requires golangci-lint) make lint # Tidy deps make tidy ``` --- ## 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: 1. ✅ Done — BFF API routes matching current Prisma queries 2. Add Meilisearch indexing + search endpoints 3. Add PostgreSQL for user features (auth, bookmarks, collections, mentions) 4. Switch frontend from Prisma to BFF API calls 5. 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