Semantic Search for Obsidian — AI-powered search, built with Astra and Mongoose
How we built an Obsidian app for semantic search with Node.js and @datastax/astra-mongoose, backed by DataStax Astra (Cassandra)
We recently announced v1.0.0 of astra-mongoose, our Mongoose plugin that allows you to use Mongoose to read/write from Apache Cassandra via DataStax Astra. What better excuse to build something cool with astra-mongoose?
Our Astra semantic search plugin for Obsidian allows you to sync notes from your local Obsidian vault to your DataStax Astra cluster. Even better, Astra’s Vectorize feature allows you to generate vector embeddings directly from Astra. We built astra-mongoose to make it dead simple to use Mongoose syntax with Astra, including support for features like vector search, directly from JavaScript.
That means no OpenAI key, no external embedding service, and no backend server. Just install the plugin, enter your Astra credentials, and start searching your notes semantically.
Whether you’re looking for “that thing I wrote about vector clocks” or “how to set up SSH keys on GitHub”, even if those exact words don’t appear in your notes, Astra can find the right doc. All from within Obsidian.
Obsidian Plugin Overview
Obsidian is a markdown-based note taking app that primarily stores your notes locally. Obsidian has a neat plugin API. And, because Obsidian uses Electron, plugins are written in JavaScript and can use npm packages like React and astra-mongoose.
The general scaffolding of an Obsidian plugin looks like the following. Obsidian recommends TypeScript for developing plugins. And plugins are just classes with hooks like `onload()`.
Here’s what the plugin looks like in Obsidian. The “Open Semantic Search” icon and “Semantic Search” panel are added by the plugin.
Working With Astra-Mongoose and Vectorize
The “backend” for this Obsidian plugin (called backend.ts, likely should be renamed since it executes in Obsidian, not a server) uses Mongoose and Astra-mongoose to sync notes to DataStax Astra. Astra-mongoose is a driver for Mongoose - it subs out Mongoose’s database API calls, like `find()` and `findOne()`, for ones that talk to Astra.
Astra-mongoose communicates with Astra using fetch() for HTTP requests, which makes integrating it with Electron slightly non-trivial due to CORS. The Obsidian plugin uses astra-mongoose’s httpOptions to make astra-mongoose use node-fetch instead, which bypasses CORS.
The `setDriver()` function just changes out the underlying database API. Mongoose syntax is still the same: you can define schemas and models as follows.
The only Astra-specific functionality above is in the `vector` path. Vectorize is Astra’s feature that allows Astra to automatically generate embeddings for you. NV-Embed-QA is an embedding model that is hosted on Astra, which you can run without any additional setup or API keys.
The `Vectorize` type is a special type that Astra-mongoose exports which can be either a string or an array of numbers (embedding). If you set `vector` to a string, Astra will convert that string to an embedding using the NV-Embed-QA model and store the result automatically. When you load a Note, `vector` will always be an embedding.
For example, below is the function that syncs an Obsidian note to Astra. Note the following code sets `vector` to a string. Astra will vectorize that string into a NV-Embed-QA model embedding
The `syncNote()` function has a couple of important details to highlight. First, Astra has certain restrictions that MongoDB does not have, and vice versa. In particular, Astra only supports `updateOne()` by `_id`, which is why notes have their path name as their `_id`.
I would prefer Notes use MongoDB ObjectIds for `_id` instead. Neither MongoDB nor Astra support updating an existing document’s `_id`, so path name for `_id` means renaming a note requires deleting the old note and upserting a new one.
The other important detail is the `useSettings()` function, which is responsible for establishing a Mongoose connection to Astra.
Handling Obsidian Settings
The semantic search plugin also has a settings view, which is where a user will enter in their Astra credentials.
I wasn’t able to find a hook that Obsidian calls when settings update. Instead, the semantic search plugin uses a `useSettings()` function that checks the current settings every time the user tries to call `syncNote()`, `syncNotes()`, `deleteNote()`, or `updateNote()`.
`useSettings()` is responsible for:
Connecting to Astra. Even though astra-mongoose uses HTTP under the hood and doesn’t maintain a persistent connection, you still need to call `connect()`.
Create a notes table in Astra, if necessary. Get the table schema definition from astra-mongoose’s `tableDefinitionFromSchema()` helper, which converts Mongoose schemas into Astra table schema definitions.
Sync indexes. Create a vector index on `vector` if necessary.
Syncing Notes in Realtime
While Obsidian doesn’t seem to have hooks for updating settings, it does let you listen for updates to individual notes. This means the semantic search plugin syncs notes to Astra as you’re typing, albeit with debouncing to avoid sending a request on every single keystroke.
Takeaways
Astra-mongoose lets you use DataStax Astra using familiar Mongoose syntax. There are a few caveats, like only being able to `updateOne()` by `_id` and missing query operators, but most simple Mongoose code “just works” with astra-mongoose.
Combined with Astra’s built-in Vectorize feature, you can build powerful AI-driven search experiences — no OpenAI key, no backend, no hassle.
'Whether you’re building a plugin, a SaaS app, or just want to explore Astra using tools you already know, astra-mongoose is ready for you.