关于 api-search-meilisearch
api-search-meilisearch 是一份面向 TypeScript 项目的 Meilisearch 使用指南型 Skill,适用于在应用中实现搜索的开发者。它涵盖异步索引任务、索引配置、过滤、排序、多索引搜索及多租户访问模式。
主要功能
- 01配置 Meilisearch TypeScript 客户端
- 02处理异步文档索引与任务
- 03设置过滤、排序和搜索属性
- 04实现过滤、地理及多索引搜索
- 05使用租户令牌实现访问控制
适用场景
- 01为应用添加全文搜索与自动补全
- 02构建带筛选和分面的导航
- 03实现附近地点搜索与距离排序
- 04在多个索引或租户间搜索
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill api-search-meilisearch安装后在 Claude Code 中输入/api-search-meilisearch确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/core.mdfiltering.mdsecurity.mdsettings.md详细介绍
Meilisearch 模式
快速指南: 使用
meilisearch(v0.56+)作为 Meilisearch v1.x 的 TypeScript 客户端。所有写入操作(添加文档、更改设置、创建索引)都是异步的——它们会返回EnqueuedTaskPromise,并在后台队列中处理。使用搜索查询中的筛选或排序之前,必须先在索引上配置filterableAttributes和sortableAttributes——这会触发完整的重新索引。使用client.index("name")获取延迟引用(不会发起网络调用),而client.getIndex("name")会从服务端获取索引。仅在脚本、种子数据和测试中对EnqueuedTaskPromise使用.waitTask()——绝不要在请求处理程序中使用。
<critical_requirements>
严重:使用此技能前
所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(必须在搜索查询中使用 filter 之前,先在索引上配置 filterableAttributes——如果某个属性不在 filterableAttributes 中,筛选会静默地返回无结果)
(必须在搜索查询中使用 sort 之前,先在索引上配置 sortableAttributes——对未配置属性进行排序会被静默忽略)
(绝不能在生产环境的请求处理程序中调用 .waitTask()——它会阻塞事件循环并轮询 Meilisearch,直到任务完成;仅在脚本、种子数据和测试中使用)
(如果文档没有 id 字段,必须显式设置主键——Meilisearch 仅在首次添加文档时自动推断主键,推断错误会导致后续批次的索引失败)
</critical_requirements>
示例
- 核心模式 -- 客户端设置、文档操作、搜索基础、任务管理、TypeScript 集成
- 过滤与分面 -- 过滤语法、分面搜索、地理搜索、可排序属性
- 索引设置 -- 排名规则、错字容错、同义词、停用词、可搜索属性、分页
- 安全与多租户 -- API 密钥、租户令牌、搜索规则、多租户模式
其他资源:
- reference.md -- 搜索参数速查表、设置默认值、决策框架、反模式
自动检测: Meilisearch、meilisearch、MeiliSearch、meilisearch-js、client.index、addDocuments、updateDocuments、multiSearch、filterableAttributes、sortableAttributes、searchableAttributes、rankingRules、typoTolerance、tenant token、generateTenantToken、EnqueuedTaskPromise、waitTask、facets、_geoRadius、_geoBoundingBox、_geoPoint、instantsearch
适用场景:
- 为应用添加全文搜索(商品搜索、内容搜索、自动补全)
- 实现分面导航(类别筛选、价格范围、属性计数)
- 构建地理位置感知搜索(查找附近地点、按距离排序)
- 实现多租户搜索,让租户共用一个索引,但只能看到自己的文档
- 同时跨多个索引搜索(多重搜索、联邦搜索)
- 实现支持错字容错的即时搜索和实时文档索引
涵盖的关键模式:
- 客户端初始化和连接管理
- 文档的 CRUD 操作与异步任务处理
- 支持过滤、排序、分面和高亮的搜索
- 使用
_geoRadius、_geoBoundingBox和距离排序进行地理搜索 - 跨索引的多重搜索和联邦搜索
- 索引设置配置(排名规则、错字容错、同义词、停用词)
- 用于多租户访问控制的租户令牌
- 用于指定搜索结果类型的 TypeScript 泛型
不适用场景:
- 在关系型数据库上进行全文搜索(简单场景可使用数据库内置的全文搜索)
- 日志聚合或分析查询(使用专用的日志/分析搜索引擎)
- 不包含关键词组件的纯向量语义搜索(使用专用的向量数据库)
- 搜索少于约 1,000 个文档(客户端筛选更简单)
理念
Meilisearch 是一种搜索引擎,而不是数据库。它会为快速检索建立文档索引,但不是事实来源。核心原则如下:
- 一切皆异步 -- 所有写入操作(文档、设置、索引管理)都会排队并异步处理。API 会立即返回任务 ID。设计应用时,不要依赖即时索引。
- 先配置,再搜索 -- 使用搜索查询前,必须先配置可过滤属性、可排序属性和可搜索属性。这会触发对所有文档的重新索引。
- 默认支持错字容错 -- Meilisearch 开箱即支持错字容错。对于特定字段(商品代码、序列号),应调整
typoTolerance设置来禁用错字容错,而不是尝试手动实现精确匹配。 - 主键至关重要 -- 每个文档都需要唯一的主键。Meilisearch 会从第一份文档中自动推断主键,但显式指定优于隐式推断。创建索引时应设置主键。
- 搜索而非查询 -- Meilisearch 针对面向用户的搜索查询进行了优化(支持错字容错、前缀匹配和排名)。它不是 SQL 的替代品。结构化查询应使用过滤器,自然语言查询则使用搜索。
核心模式
模式 1:客户端设置
使用主机和 API 密钥初始化客户端。使用 client.index() 获取延迟加载的本地引用(不会发起网络调用)——优先使用它,而不是会请求服务器的 client.getIndex()。
// Good Example -- Typed client setup
import { Meilisearch } from "meilisearch";
function createSearchClient(): Meilisearch {
const host = process.env.MEILISEARCH_URL;
const apiKey = process.env.MEILISEARCH_API_KEY;
if (!host) {
throw new Error("MEILISEARCH_URL environment variable is required");
}
return new Meilisearch({ host, apiKey });
}
export { createSearchClient };
优点: 验证环境变量,使用命名导出,apiKey 为可选项(Meilisearch 在开发环境中允许未经身份验证的访问)
// Bad Example -- Hardcoded credentials
import { Meilisearch } from "meilisearch";
const client = new Meilisearch({
host: "http://localhost:7700",
apiKey: "masterKey123",
});
缺点: 硬编码的主机和 API 密钥会泄露到版本控制中,主密钥也会暴露(在生产环境中应使用范围受限的 API 密钥)
请参阅 examples/core.md,了解健康检查、AbortController 的用法以及自定义请求配置。
模式 2:文档索引(异步)
所有文档操作都会返回 EnqueuedTaskPromise。文档不会立即可供搜索——它们会进入任务队列。
// Good Example -- Add documents with explicit primary key
interface Product {
productId: string;
name: string;
description: string;
price: number;
categories: string[];
}
const index = client.index<Product>("products");
// First add: set primary key explicitly
const task = await index.addDocuments(products, { primaryKey: "productId" });
// task.taskUid: number -- use this to track progress
优点: 显式指定主键可避免自动推断问题,TypeScript 泛型可确保文档结构的类型安全
// Bad Example -- Relying on auto-inference
const index = client.index("products");
await index.addDocuments(products); // No primary key specified
// If first document has both 'id' and 'productId', Meilisearch guesses wrong
缺点: Meilisearch 会根据第一个文档推断主键——如果推断错误,后续所有添加操作都可能因主键冲突而失败
请参阅 examples/core.md,了解更新、删除、批处理和任务管理模式。
模式 3:使用筛选器搜索
必须先配置 filterableAttributes,才能使用筛选器。筛选器语法使用类似 SQL 的运算符,包括 AND/OR/NOT。
// Good Example -- Search with filter and sort
const MIN_PRICE = 10;
const MAX_PRICE = 100;
const results = await index.search("wireless headphones", {
filter: `price >= ${MIN_PRICE} AND price <= ${MAX_PRICE} AND categories = "electronics"`,
sort: ["price:asc"],
limit: 20,
});
// results.hits: Product[], results.estimatedTotalHits: number
优点: 使用命名常量表示筛选值,将文本搜索与结构化筛选结合,并显式设置结果数量上限
// Bad Example -- Filtering without configuring filterableAttributes
const index = client.index("products");
// MISSING: await index.updateFilterableAttributes(["price", "categories"])
const results = await index.search("headphones", {
filter: "price < 50", // Returns 0 results -- silently fails!
});
缺点: 如果属性不在 filterableAttributes 中,筛选器会返回空结果且不报错——这是 Meilisearch 最常见的易错点
请参阅 examples/filtering.md,了解分面搜索、地理筛选器和高级筛选器语法。
模式 4:多重搜索
在单个请求中跨多个索引搜索。联邦搜索会将结果合并为统一列表。
// Good Example -- Multi-search across indexes
const results = await client.multiSearch({
queries: [
{ indexUid: "products", q: "laptop", limit: 5 },
{ indexUid: "articles", q: "laptop review", limit: 5 },
],
});
// results.results[0].hits -- products
// results.results[1].hits -- articles
// Federated search -- merged results
const federated = await client.multiSearch({
federation: {},
queries: [
{ indexUid: "products", q: "laptop" },
{ indexUid: "articles", q: "laptop" },
],
});
// federated.hits -- single merged list sorted by relevance
优点: 通过单个网络请求搜索多个索引,联邦搜索可对跨索引结果进行统一排名
请参阅 examples/core.md,了解带查询权重的联邦搜索。
模式 5:任务管理
写入操作是异步的。使用任务 UID 跟踪进度。仅在脚本和测试中使用 .waitTask()。
// Good Example -- Task tracking in a seed script
const task = await index.addDocuments(products, {
primaryKey: "productId",
});
// In scripts/seeds: wait for completion
const completed = await task.waitTask();
if (completed.status === "failed") {
throw new Error(`Indexing failed: ${completed.error?.message}`);
}
console.log(`Indexed ${completed.details?.indexedDocuments} documents`);
优点: 在种子数据脚本中(而非请求处理程序中)使用 .waitTask(),检查错误状态并查看任务详情
// Bad Example -- Waiting in a request handler
app.post("/products", async (req, res) => {
const task = await index.addDocuments([req.body]);
await task.waitTask(); // BLOCKS the request until Meilisearch processes the task!
res.json({ success: true });
});
缺点: .waitTask() 会反复轮询 Meilisearch,阻塞请求处理程序——任务可能因队列深度不同而耗时数秒或数分钟
请参阅 examples/core.md,了解批量任务管理和任务状态轮询。
模式 6:索引设置配置
更改设置会触发完整的重新索引。请在添加文档之前配置设置,以避免重新索引。
// Good Example -- Configure index before adding documents
const index = client.index("products");
// Step 1: Configure settings (triggers re-index)
await index
.updateSettings({
filterableAttributes: ["price", "categories", "brand", "inStock"],
sortableAttributes: ["price", "createdAt"],
searchableAttributes: ["name", "description", "brand"],
rankingRules: [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"price:asc",
],
typoTolerance: {
disableOnAttributes: ["sku", "barcode"],
},
synonyms: {
phone: ["smartphone", "mobile"],
laptop: ["notebook"],
},
})
.waitTask(); // OK in setup script
// Step 2: Add documents (indexes with correct settings)
await index.addDocuments(products, { primaryKey: "productId" });
优点: 在添加文档前配置设置可避免重复重新索引,在精确匹配字段上禁用拼写容错,并为常见别名定义同义词
请参阅 examples/settings.md,了解所有设置选项、停用词和分页配置。
<decision_framework>
决策框架
该选择哪种搜索方式?
What kind of search do I need?
-- Single index, text query? -> index.search(query, options)
-- Multiple indexes, separate results? -> client.multiSearch({ queries })
-- Multiple indexes, merged results? -> client.multiSearch({ federation: {}, queries })
-- Browse/filter without text? -> index.search("", { filter, sort }) (placeholder search)
筛选还是搜索?
How should users find data?
-- Natural language, typo-tolerant? -> Use the `q` parameter (search)
-- Exact attribute matching? -> Use `filter` parameter
-- Both? -> Combine: search("query", { filter: "category = 'X'" })
-- Browsing without a query? -> Placeholder search: search("", { filter, sort })
分页策略?
How should I paginate results?
-- Infinite scroll / load more? -> Use offset + limit (default)
-- Page numbers (page 1, 2, 3)? -> Use page + hitsPerPage
-- NOTE: Default maxTotalHits is 1000 -- increase in pagination settings if needed
任务管理策略?
How should I handle async operations?
-- Seed script / migration? -> .waitTask() is fine
-- Test setup? -> .waitTask() to ensure data is ready
-- API request handler? -> Fire-and-forget, return task UID to client
-- Need confirmation? -> Return taskUid, let client poll GET /tasks/:uid
</decision_framework>
<red_flags>
危险信号
高优先级问题:
- 未先配置
filterableAttributes/sortableAttributes就进行筛选或排序——筛选会静默返回空结果,排序会被静默忽略 - 在生产请求处理程序中使用
.waitTask()——会阻塞事件循环,导致高负载时请求超时 - 在客户端代码中使用主密钥——会暴露完整的管理员权限;请使用仅限搜索的 API 密钥或租户令牌
- 未显式设置主键——Meilisearch 会根据第一个文档自动推断主键,可能选错字段,导致后续所有索引操作失败
中优先级问题:
- 添加文档后才配置设置——会触发所有文档的完整重新索引,大型数据集可能需要几分钟
- 超出默认的
maxTotalHits: 1000分页限制——搜索结果会静默限制在 1000 条;如需更深层分页,可通过设置中的pagination.maxTotalHits提高上限 - 在筛选条件中使用
AND/OR却未加括号——AND的优先级高于OR,导致筛选结果出乎预期 - 未处理任务失败——失败的任务不会更改索引,但只有检查任务状态才能看到错误
易踩问题与边界情况:
- 地理搜索必须在
filterableAttributes中包含_geo——仅添加带有_geo字段的文档还不够,必须显式列出该属性 client.index("name")不会创建索引,也不会验证索引是否存在——它只会返回本地引用;要实际创建索引,请使用client.createIndex("name")- 搜索空字符串(
search(""))是有效的“占位搜索”——会返回所有符合筛选条件的文档,适用于浏览和分面导航 - Meilisearch 任务队列上限约为 10 GiB——如果队列已满,新的写入操作会因
no_space_left_on_device而失败;请定期删除已完成的任务 - 同义词不适用于筛选——即使已将“phone”和“smartphone”配置为同义词,按“phone”筛选也不会匹配包含“smartphone”的文档
_geo字段格式严格:必须为{ lat: number, lng: number }——使用longitude而非lng会导致invalid_document_geo_field错误- 更改设置(如 filterableAttributes 等)也会排入任务队列——不会立即生效;依赖新设置前,请等待任务完成
hitsPerPage和page参数会覆盖offset/limit——不要在同一查询中混用这两种分页方式- 默认
maxTotalHits为 1000——即使使用基于 offset 的分页,若不提高此设置,也无法访问位置 1000 之后的文档
</red_flags>
<critical_reminders>
重要提醒
所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(必须先在索引上配置 filterableAttributes,再在搜索查询中使用 filter——如果属性不在 filterableAttributes 中,筛选会静默返回空结果)
(必须先在索引上配置 sortableAttributes,再在搜索查询中使用 sort——对未配置的属性进行排序会被静默忽略)
(绝不能在生产请求处理程序中调用 .waitTask()——它会阻塞事件循环,轮询 Meilisearch 直到任务完成;仅在脚本、种子数据和测试中使用)
(当文档缺少 id 字段时,必须显式设置主键——Meilisearch 仅在首次添加文档时自动推断主键,推断错误会导致后续批次的索引操作失败)
不遵循这些规则会导致搜索静默失败、请求超时和索引错误。
</critical_reminders>