Adding Search
slingshot-search adds full-text entity search to any Slingshot app. It plugs into the entity model so your entities are indexed automatically as they’re created and updated.
Install
Section titled “Install”bun add @lastshotlabs/slingshot-searchAdd the plugin
Section titled “Add the plugin”import { defineApp } from '@lastshotlabs/slingshot';import { createAuthPlugin } from '@lastshotlabs/slingshot-auth';import { createSearchPackage } from '@lastshotlabs/slingshot-search';
export default defineApp({ plugins: [ createAuthPlugin({ auth: { roles: ['user'], defaultRole: 'user' }, db: { auth: 'memory', sessions: 'memory', oauthState: 'memory' }, }), ], packages: [ createSearchPackage({ providers: { default: { provider: 'db-native' }, }, }), ],});Register entities for indexing
Section titled “Register entities for indexing”Add a search block to any entity to make it indexable. Then declare a search operation to expose a search endpoint:
import { defineEntity, defineOperations, field, op } from '@lastshotlabs/slingshot';
export const Post = defineEntity('Post', { namespace: 'blog', fields: { id: field.string({ primary: true, default: 'uuid' }), title: field.string(), body: field.string(), tags: field.string({ optional: true }), status: field.enum(['draft', 'published'] as const), }, search: { fields: { title: { searchable: true, weight: 3 }, body: { searchable: true }, tags: { searchable: true }, status: { filterable: true }, }, syncMode: 'event-bus', // keep the index in sync via CRUD events }, routes: { defaults: { auth: 'none' }, },});
// The search endpoint lives in operations, not in the entity routes blockexport const postOperations = defineOperations(Post, { search: op.search({ fields: ['title', 'body'], filter: { status: 'published' }, // only surface published posts paginate: true, }),});The operation’s filter config limits results to published posts. The search plugin keeps the index in sync as entities are created or updated via syncMode: 'event-bus'.
Under the hood that sync path now depends on registry-backed event definitions. Search consumers
should publish through ctx.events.publish(...) or entity route config, not through ad hoc raw bus
payloads or legacy client-safe registration.
Query the search endpoint
Section titled “Query the search endpoint”# Basic searchcurl "http://localhost:3000/posts/search?q=slingshot"
# With paginationcurl "http://localhost:3000/posts/search?q=slingshot&page=2&limit=20"
# Filtered (additional filters beyond the entity-level filter)curl "http://localhost:3000/posts/search?q=slingshot&authorId=abc123"Response:
{ "results": [{ "id": "...", "title": "Getting started with Slingshot", "score": 0.92 }], "total": 14, "page": 1, "pages": 2}Global search across entity types
Section titled “Global search across entity types”Register a global search endpoint that queries across multiple entity types:
createSearchPackage({ adapter: 'memory', globalSearch: { enabled: true, path: '/search', types: ['post', 'thread', 'reply'], },});curl "http://localhost:3000/search?q=authentication&types=post,thread"Production search backends
Section titled “Production search backends”Swap the adapter without changing any entity definitions or query code:
bun add meilisearchcreateSearchPackage({ adapter: 'meilisearch', url: 'http://localhost:7700', apiKey: process.env.MEILI_API_KEY!,});bun add @elastic/elasticsearchcreateSearchPackage({ adapter: 'elasticsearch', url: 'http://localhost:9200', apiKey: process.env.ES_API_KEY!,});bun add typesensecreateSearchPackage({ adapter: 'typesense', nodes: [{ host: 'localhost', port: 8108, protocol: 'http' }], apiKey: process.env.TYPESENSE_API_KEY!,});Re-indexing
Section titled “Re-indexing”If you add search to an existing app, trigger a one-time re-index:
slingshot reindex --entity postslingshot reindex --allWhere to go next
Section titled “Where to go next”- slingshot-search — full adapter and config reference
- Config-Driven Domain — defining entities with search operations