# Framework Integration Guide
Brainy is **framework-friendly** - designed to drop into the server side of any modern JavaScript framework. This guide shows you how to integrate Brainy into framework-based apps.
> **Runtime**: Brainy 8.0 runs on Node.js 22+ and Bun (server-side only). It is not a browser library. In framework apps, run Brainy in API routes, server components, server actions, loaders, or a dedicated backend service - never in client-side bundles.
## 🎯 Why Server-Side?
Brainy embeds an HNSW vector index, a graph engine, and a filesystem-backed persistence layer. These belong on the server:
- **Zero configuration**: Just `import { Brainy } from '@soulcraft/brainy'`
- **Auto storage detection**: `new Brainy()` auto-selects filesystem persistence on Node
- **Cleaner code**: No browser polyfills, no conditional client/server imports
- **Better DX**: One instance shared across your server routes
## 🚀 Quick Start
### Install Brainy
```bash
npm install @soulcraft/brainy
```
### Basic Integration
```javascript
import { Brainy } from '@soulcraft/brainy'
// Run on the server (API route, server component, backend service)
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
// Add data
await brain.add({
data: "Framework integration is awesome!",
type: "concept",
metadata: { framework: "any" }
})
// Search
const results = await brain.find("framework integration")
```
## ⚛️ React Integration
Brainy runs on the server, so a React client component talks to it through an API endpoint (see the Next.js API route below). The hook fetches results; it never instantiates Brainy in the browser.
### Basic Hook Pattern
```jsx
import { useState, useCallback } from 'react'
function useBrainySearch(endpoint = '/api/search') {
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
const search = useCallback(async (query) => {
if (!query) return
setLoading(true)
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
})
const { results } = await res.json()
setResults(results)
} finally {
setLoading(false)
}
}, [endpoint])
return { results, loading, search }
}
// Usage in component
function SearchComponent() {
const { results, loading, search } = useBrainySearch()
return (
search(e.target.value)}
/>
{loading &&
Searching...
}
{results.map(result => (
{result.data}
Score: {(result.score * 100).toFixed(1)}%
))}
)
}
```
### Shared Server Instance
On the server, create one Brainy instance and reuse it across requests. This module is imported only by server code (API routes, server components), never by client components:
```javascript
// lib/brain.server.js
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
}
return brainPromise
}
```
## 🟢 Vue.js Integration
Vue components call a server endpoint (see the Nuxt server route in the [Vue.js Integration Guide](vue-integration.md)); Brainy itself runs on the server.
### Composition API (client component)
```vue
{{ result.data }}
Score: {{ (result.score * 100).toFixed(1) }}%
```
### Shared Server Instance
On the server, create one Brainy instance and reuse it across requests:
```javascript
// server/brain.js (server-only module)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
}
return brainPromise
}
```
## 🅰️ Angular Integration
The Angular service calls your backend over HTTP; Brainy lives in that backend, not in the browser.
### Service Pattern (calls the backend)
```typescript
// brainy.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
@Injectable({
providedIn: 'root'
})
export class BrainyService {
constructor(private http: HttpClient) {}
search(query: string): Observable<{ results: any[] }> {
return this.http.post<{ results: any[] }>('/api/search', { query })
}
add(data: any, type: string, metadata?: any): Observable<{ id: string }> {
return this.http.post<{ id: string }>('/api/add', { data, type, metadata })
}
}
```
```typescript
// search.component.ts
import { Component } from '@angular/core'
import { BrainyService } from './brainy.service'
@Component({
selector: 'app-search',
template: `
{{ result.data }}
Score: {{ (result.score * 100).toFixed(1) }}%
`
})
export class SearchComponent {
query = ''
results: any[] = []
constructor(private brainyService: BrainyService) {}
search() {
if (!this.query) return
this.brainyService.search(this.query).subscribe(({ results }) => {
this.results = results
})
}
}
```
The matching backend endpoint uses Brainy directly (Node/Bun):
```typescript
// server: api/search
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy() // auto-detects filesystem persistence on Node
await brain.init()
export async function handleSearch(query: string) {
return await brain.find(query)
}
```
## 🚀 Next.js Integration
In Next.js, Brainy lives in server code only: API routes, server components, or server actions. Create one shared instance in a server-only module.
### Shared Server Instance
```javascript
// lib/brain.server.js (imported only by server code)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
await brain.init()
return brain
})()
}
return brainPromise
}
```
### API Routes
```javascript
// app/api/search/route.js
import { getBrain } from '@/lib/brain.server'
export async function POST(request) {
const { query } = await request.json()
const brain = await getBrain()
const results = await brain.find(query)
return Response.json({ results })
}
```
### Server Action
```javascript
// app/actions.js
'use server'
import { getBrain } from '@/lib/brain.server'
export async function search(query) {
const brain = await getBrain()
return await brain.find(query)
}
```
## 🔷 SvelteKit Integration
Brainy runs in a server-only module (`*.server.js`); the component fetches results from an endpoint.
```javascript
// src/lib/server/brain.js (server-only — note the .server suffix)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
const brain = new Brainy() // auto-detects filesystem persistence
await brain.init()
return brain
})()
}
return brainPromise
}
```
```javascript
// src/routes/api/search/+server.js
import { json } from '@sveltejs/kit'
import { getBrain } from '$lib/server/brain'
export async function POST({ request }) {
const { query } = await request.json()
const brain = await getBrain()
return json({ results: await brain.find(query) })
}
```
```svelte
{#each results as result}
{result.data}
Score: {(result.score * 100).toFixed(1)}%
{/each}
```
## 🌟 Solid.js Integration
The component calls a server endpoint (use SolidStart server routes, or any backend, to host Brainy):
```jsx
import { createSignal } from 'solid-js'
function SearchComponent() {
const [query, setQuery] = createSignal('')
const [results, setResults] = createSignal([])
const search = async () => {
if (!query()) return
const res = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: query() })
})
setResults((await res.json()).results)
}
return (
)
}
```
## 📦 Bundler Configuration
Brainy is a server-side dependency, so keep it out of client bundles. Import it only from server-only modules (`*.server.js`, API routes, server components, server actions). If your bundler ever tries to pull Brainy into a client bundle, that's a sign it's being imported from a client component — move the import to a server module.
For server builds, mark Brainy as external so the bundler doesn't inline it:
```javascript
// vite.config.js (SSR build)
import { defineConfig } from 'vite'
export default defineConfig({
ssr: {
external: ['@soulcraft/brainy']
}
})
```
```javascript
// rollup.config.js (server bundle)
export default {
external: ['@soulcraft/brainy', 'node:fs', 'node:path', 'node:crypto']
}
```
## 🌐 SSR/SSG Considerations
### Server-Side Rendering
Instantiate Brainy on the server and feed its results into the rendered page. Never construct it in client code.
```javascript
// Server-side data loading (framework loader / getServerSideProps / load fn)
import { getBrain } from './brain.server'
export async function load({ url }) {
const brain = await getBrain()
const query = url.searchParams.get('q') ?? ''
const results = query ? await brain.find(query) : []
return { results }
}
```
### Static Site Generation
```javascript
// For build-time usage (runs in Node during the build)
import { Brainy } from '@soulcraft/brainy'
export async function generateStaticProps() {
const brain = new Brainy({
storage: { type: 'filesystem', path: './content' }
})
await brain.init()
// Build search index (paginate with { limit, offset } for larger stores)
const allContent = await brain.find({ limit: 1000 })
return {
props: { searchIndex: allContent }
}
}
```
## 🔧 Framework-Specific Tips
### React
- Keep components client-side and call a Brainy-backed API route
- Use `useCallback` for fetch handlers to prevent re-renders
- Debounce keystroke-driven searches before hitting the endpoint
### Vue
- Components call an endpoint; the shared instance lives in a server module
- Consider Pinia for caching results client-side
- Debounce reactive search queries
### Angular
- Use `HttpClient` and RxJS to call the backend
- Hold the shared Brainy instance in your Node backend, not the app
- Consider lazy loading search features in feature modules
### Next.js
- Put Brainy in server-only modules (`*.server.js`), API routes, or server actions
- Reuse one shared instance across requests
- Implement proper error boundaries for failed fetches
## 🚨 Common Issues & Solutions
### Issue: "fs module not found" / "crypto is not defined" in the browser
**Cause**: Brainy was imported into a client bundle. Brainy 8.0 is a server-side library (Node 22+/Bun) and uses Node built-ins like `fs` and `crypto`.
**Solution**: Import Brainy only from server code — server-only modules (`*.server.js`), API routes, server components, or server actions. From client components, call those endpoints instead.
### Issue: Large client bundle size
**Cause**: A client module is pulling in Brainy.
**Solution**: Move the `import { Brainy } from '@soulcraft/brainy'` into a server-only module so it never reaches the browser bundle.
### Issue: SSR hydration mismatch
**Solution**: Run the search on the server (loader / server action / API route) and pass the results down as props, so server and client render the same markup.
## 🎯 Best Practices
1. **Initialize Once**: Create one shared Brainy instance per server process, not per request
2. **Server-Only**: Import Brainy only from server modules — never from client components
3. **Endpoint Boundary**: Expose search/add through API routes or server actions
4. **Handle Loading**: Show loading states in the client while the fetch is in flight
5. **Error Handling**: Catch and surface failed endpoint calls gracefully
6. **Storage**: Use `filesystem` for persistence (the default on Node) or `memory` for ephemeral/tests
## 📚 Next Steps
- [Next.js Integration Guide](nextjs-integration.md) - Detailed Next.js examples
- [Vue.js Integration Guide](vue-integration.md) - Complete Vue.js patterns
- [API Reference](../api/README.md) - Complete API documentation
- [Production Deployment](../deployment/CLOUD_DEPLOYMENT_GUIDE.md) - Deploy to production
## 🤝 Community Examples
Check out community examples in the [examples repository](https://github.com/soulcraftlabs/brainy-examples):
- React + TypeScript starter
- Vue 3 + Composition API
- Next.js full-stack app
- Svelte SPA with search
- Angular enterprise app