A local-first notes app with hybrid search and RAG chat. Everything runs on your machine using Ollama for embeddings and LLM inference.
- Next.js 16 (App Router, TypeScript, Tailwind CSS)
- tRPC for type-safe API layer
- Drizzle ORM + better-sqlite3 for persistence
- sqlite-vec for vector similarity search
- SQLite FTS5 for lexical full-text search
- Vercel AI SDK + Ollama for embeddings and chat
- Husky + lint-staged + commitlint for Conventional Commits enforcement
- Node.js 20+
- Ollama installed and running
Pull the required models:
ollama pull llama3.1:8b
ollama pull nomic-embed-textnpm install
cp .env.example .env.local # optional — defaults work without it
npx drizzle-kit generate # generate migration SQL
sqlite3 ./data/engram.db < drizzle/<generated-file>.sql # apply it
npm run devNote:
npx drizzle-kit pushis broken — see Schema Changes below. Usedrizzle-kit generate+sqlite3instead. The virtual tables (note_embeddings,note_fts) are created automatically on server start.
Open http://localhost:3000.
Runs the Next.js app and Ollama in containers. Models are pulled automatically on first run.
mkdir -p data
docker compose up --buildOpen http://localhost:3000.
Data persists between runs — the SQLite database is bind-mounted to ./data/ and Ollama models are stored in a named volume. Use docker compose down -v only if you want to wipe model storage.
Runs only Ollama in Docker while the app runs natively. Gives full hot reload speed with Metal GPU acceleration on macOS.
docker compose -f docker-compose.dev.yml up
npm run devNote: On macOS, Ollama inside Docker runs on CPU only (no Metal GPU). Inference with
llama3.1:8bwill be slow. For daily development, prefer native Ollama or switch to a smaller model viaOLLAMA_CHAT_MODEL=llama3.2:3bin.env.local.
See .env.example for all available options. Defaults work out of the box if Ollama is running locally.
| Variable | Default | Description |
|---|---|---|
OLLAMA_BASE_URL |
http://localhost:11434/v1 |
Ollama API endpoint |
OLLAMA_CHAT_MODEL |
llama3.1:8b |
Model used for RAG chat |
OLLAMA_EMBEDDING_MODEL |
nomic-embed-text |
Model used for embeddings |
EMBEDDING_DIMENSION |
768 |
Vector dimension (must match embedding model) |
DATABASE_PATH |
./data/engram.db |
SQLite database file path |
drizzle-kit push is broken — it introspects the database without loading sqlite-vec, hits the note_embeddings virtual table and crashes. Use drizzle-kit generate to produce a SQL migration file and apply it manually:
npx drizzle-kit generate
sqlite3 ./data/engram.db < drizzle/<generated-file>.sqlrm -f ./data/engram.db ./data/engram.db-shm ./data/engram.db-wal
npx drizzle-kit generate
sqlite3 ./data/engram.db < drizzle/<generated-file>.sql
npm run dev # virtual tables are recreated automatically on startupnpm testInstall act and Docker, then:
act pull_requestsrc/
app/ # Next.js pages and API routes
api/chat/ # Streaming RAG chat endpoint
api/trpc/ # tRPC handler
chat/ # Chat page
notes/new/ # Create note page
search/ # Hybrid search page
components/ # React components
lib/
ai/ # Ollama provider, embedding generation
db/ # Drizzle schema, sqlite-vec helpers, FTS5 helpers
trpc/ # tRPC router, React provider, server helpers
data/ # SQLite database (gitignored)
scripts/
init-ollama.sh # Pulls required Ollama models on first Docker run