A Next.js project demonstrating Retrieval-Augmented Generation (RAG) with vector search capabilities.
bun iInstall PostgreSQL and the pgvector extension:
# macOS with Homebrew
brew install postgresql@18
brew install pgvector
# Start PostgreSQL
brew services start postgresql@18
# Create the database
createdb unlearn-rag-course
# Enable the pgvector extension
psql -d unlearn-rag-course -c "CREATE EXTENSION IF NOT EXISTS vector;"Create a .env.local file in the project root:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/unlearn-rag-course
VOYAGE_API_KEY=your_voyage_api_key_here
GROQ_API_KEY=your_groq_api_key_hereWhere to get your API keys:
- Voyage AI — Sign up at voyageai.com and create an API key from the dashboard
- Groq — Sign up at groq.com and generate an API key from your account settings
bun db:migrateThis creates all tables: documents, chunks, conversations, messages, and message_sources.
Process the MDN documentation into chunks:
bun chunk-docsThis creates chunks.json from the markdown files in mdn-js-docs/.
bun db:seedThis populates the database with MDN JavaScript documentation chunks (33 documents, ~1,180 chunks). All inserts run inside a database transaction — if any step fails, the database rolls back to its previous state.
Add your Voyage AI API key to .env.local:
VOYAGE_API_KEY=your_key_hereThen generate embeddings for all chunks:
bun db:embeddingsThis sends chunks to Voyage AI in batches of 128 and stores the resulting 1024-dimensional vectors in the chunks.embedding column. The script only processes chunks that don't already have embeddings, so it's safe to re-run.
Run automated evaluations of the RAG system:
npm run eval # Run all evaluations
npm run eval:01 # Retrieval accuracy only
npm run eval:02 # Context adherence onlyView detailed results:
npm run eval:view # View all results
npm run eval:view:01 # View retrieval results
npm run eval:view:02 # View context adherence resultsSee evaluation/README.md for setup details.
bun rag-query "What is a closure in JavaScript?"This performs semantic search and queries the Groq LLM with retrieved context. Supports --limit and --threshold flags:
bun rag-query "your question" --limit=10 --threshold=0.6bun devOpen http://localhost:3000 in your browser.
This project uses Drizzle ORM with PostgreSQL.
The database uses branded types for type-safe IDs. Primary keys use either UUID or text with semantic brand tags to prevent mixing up different ID types at compile time.
| Table | Purpose |
|---|---|
documents |
Source documents (MDN guides) |
chunks |
Document chunks with vector embeddings for similarity search |
conversations |
Chat conversations |
messages |
Chat messages (user and AI) |
message_sources |
Links between AI messages and source chunks (citations) |
# Generate migrations from schema changes
bun db:generate
# Apply pending migrations
bun db:migrate
# Seed the database with chunk data
bun db:seed
# Debug migration failures
bun db:debug-migrations
# Sync migration journal after manual fixes
bun db:sync-migrations
# Rollback the last migration
bun db:rollbackDatabase scripts live in scripts/db/. Configuration is in drizzle.config.ts.
- Server logic (
src/lib/server/) — Pure functions for embedding generation, semantic search, and RAG. Used by both CLI scripts and the Next.js API route. - Shared constants (
src/lib/shared/) — Configuration like default LLM model, shared between server and client. - API route (
src/app/api/chat/) — Next.js route handler that validates requests and orchestrates the RAG pipeline. - CLI scripts (
scripts/,scripts/db/) — Thin wrappers aroundsrc/lib/server/functions for command-line usage. General scripts inscripts/, database-specific scripts inscripts/db/.
bun dev # Start development server
bun build # Production build
bun type-check # TypeScript type checking
bun lint # Run Biome linter
bun lint:fix # Fix linting issues
bun check-all # Run type-check + lint
bun chunk-docs # Process and chunk documents
bun db:generate # Generate Drizzle migrations
bun db:migrate # Apply database migrations
bun db:seed # Seed database with chunk data
bun db:embeddings # Generate Voyage AI embeddings for chunks
bun db:debug-migrations # Debug migration failures
bun db:sync-migrations # Sync migration journal
bun db:rollback # Rollback last migration
bun semantic-search "your question" # Search chunks by semantic similarity
bun rag-query "your question" # RAG query with LLM response
npm run eval # Run all Promptfoo evaluations
npm run eval:01 # Run retrieval evaluation only
npm run eval:02 # Run context adherence evaluation only
npm run eval:view # View all evaluation results
npm run eval:view:01 # View retrieval results
npm run eval:view:02 # View context adherence resultsFor detailed usage, options, and prerequisites for each script, see scripts/README.md.
- Framework: Next.js 16 (App Router)
- Styling: Tailwind CSS
- Database: PostgreSQL + Drizzle ORM
- Vector Search: pgvector
- Embeddings: Voyage AI
- AI/LLM: Vercel AI SDK + Groq
- Runtime: Bun
- Linting: Biome
voyageaiESM build: Thevoyageainpm package has a known ESM import bug (upstream issue). The project usesserverExternalPackagesinnext.config.tsas a workaround.- Dependency advisories: See SECURITY.md for current dependency vulnerability status.
This project uses free-tier APIs with rate limits:
| Service | Limit | Reset |
|---|---|---|
Groq (llama-3.3-70b-versatile) |
100,000 tokens/day | Daily |
| Voyage AI | Check your plan | Varies |
If you hit Groq's rate limit, the error message will show how long to wait. Consider upgrading to a paid tier for production use.