Meilisearch is a fast, open-source search engine written in Rust that provides instant typo-tolerant full-text search through a RESTful API. Pairing it with Go gives you a production-grade search layer for any application โ from e-commerce product lookup to documentation search to log exploration. This guide covers everything from basic client setup to advanced hybrid search, index management, and production hardening.
What is Meilisearch?
Meilisearch indexes JSON documents and returns ranked search results in milliseconds. Its standout features include typo tolerance by default, customizable ranking rules, faceted search, filters, and native vector (hybrid) search support in v1.13+. It can run self-hosted or as a managed cloud service, and the official Go client wraps the entire REST API with a clean, idiomatic interface.
Prerequisites
- Go 1.21 or later (for
slog,contextimprovements, and generics support). - A running Meilisearch instance โ local (v1.13+) or cloud.
- The Go client:
go get github.com/meilisearch/meilisearch-go@latest.
Project Setup and Dependency Management
mkdir meilisearch-go-app
cd meilisearch-go-app
go mod init meilisearch-go-app
go get github.com/meilisearch/meilisearch-go@latest
The official client handles HTTP transport, request serialization, and task polling. Pin your dependency with go mod tidy after each update to avoid surprises during deployments.
Client Initialization
Basic Client
package main
import (
"context"
"fmt"
"log"
"log/slog"
"time"
"github.com/meilisearch/meilisearch-go"
)
func main() {
client := meilisearch.New("http://localhost:7700", meilisearch.WithAPIKey("master_key"))
health, err := client.Health()
if err != nil {
log.Fatalf("meilisearch unreachable: %v", err)
}
fmt.Printf("Meilisearch status: %s | version: %s\n", health.Status, health.Version)
}
Client With Multiple Configuration Options
func newSearchClient() meilisearch.ServiceManager {
cfg := &meilisearch.ClientConfig{
Host: "https://search.example.com",
APIKey: "prod_key_abc123",
Timeout: 30 * time.Second,
}
return meilisearch.NewClient(*cfg)
}
For advanced needs โ custom TLS, connection pooling, or retry โ wrap the underlying HTTP client:
func newHardenedClient() meilisearch.ServiceManager {
transport := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS13},
DisableCompression: false,
}
httpClient := &http.Client{
Transport: transport,
Timeout: 60 * time.Second,
}
cfg := &meilisearch.ClientConfig{
Host: "https://search.example.com",
APIKey: "prod_key_abc123",
HttpClient: httpClient,
}
return meilisearch.NewClient(*cfg)
}
CRUD Operations
Struct and Document Preparation
Use JSON struct tags to control field mapping. The primary key must be unique and non-null โ Meilisearch defaults to id if not explicitly set.
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Price float64 `json:"price"`
InStock bool `json:"in_stock"`
Tags []string `json:"tags"`
CreatedAt time.Time `json:"created_at"`
}
var products = []Product{
{ID: 1, Name: "Wireless Mouse", Description: "Ergonomic 2.4G wireless mouse with 6 buttons", Category: "Electronics", Price: 29.99, InStock: true, Tags: []string{"mouse", "wireless", "ergonomic"}, CreatedAt: time.Now()},
{ID: 2, Name: "Mechanical Keyboard", Description: "RGB backlit mechanical keyboard with Cherry MX switches", Category: "Electronics", Price: 89.99, InStock: true, Tags: []string{"keyboard", "mechanical", "rgb"}, CreatedAt: time.Now()},
{ID: 3, Name: "USB-C Hub", Description: "7-in-1 USB-C hub with HDMI, USB 3.0, SD card reader", Category: "Accessories", Price: 34.99, InStock: false, Tags: []string{"usb", "hub", "adapter"}, CreatedAt: time.Now()},
}
Create Index and Add Documents
func createIndexAndAddDocs(client meilisearch.ServiceManager) {
// Create index with explicit primary key
idx, err := client.CreateIndex(&meilisearch.IndexConfig{
Uid: "products",
PrimaryKey: "id",
})
if err != nil {
slog.Error("create index failed", "err", err)
return
}
slog.Info("index created", "uid", idx.UID)
// Add documents โ returns a task
task, err := idx.AddDocuments(products)
if err != nil {
slog.Error("add documents failed", "err", err)
return
}
// Block until Meilisearch finishes indexing
task, err = client.WaitForTask(task.TaskUID, 10*time.Second)
if err != nil {
slog.Error("task did not complete", "err", err)
return
}
slog.Info("documents indexed", "task_uid", task.TaskUID, "status", task.Status)
}
Update (Partial) and Replace (Full) Documents
UpdateDocuments performs a partial update โ only provided fields are changed. AddDocuments with an existing primary key replaces the entire document.
func updateAndReplace(idx meilisearch.IndexManager) {
// Partial update: only change Price and Tags
partial := []map[string]interface{}{
{"id": 1, "price": 24.99, "tags": []string{"mouse", "wireless", "sale"}},
}
task, err := idx.UpdateDocuments(partial)
if err != nil {
slog.Error("update failed", "err", err)
return
}
slog.Info("documents updated", "task_uid", task.TaskUID)
// Full replace: the entire document is overwritten
full := Product{
ID: 1, Name: "Wireless Mouse V2",
Description: "Updated ergonomic wireless mouse with silent clicks",
Category: "Electronics", Price: 34.99, InStock: true,
Tags: []string{"mouse", "wireless", "silent"},
}
task, err = idx.AddDocuments([]Product{full})
if err != nil {
slog.Error("replace failed", "err", err)
return
}
slog.Info("document replaced", "task_uid", task.TaskUID)
}
Delete Documents
func deleteDocuments(idx meilisearch.IndexManager) {
// Delete by single primary key
task, err := idx.DeleteDocument(3)
if err != nil {
slog.Error("delete failed", "err", err)
return
}
slog.Info("document deleted", "task_uid", task.TaskUID)
// Delete by filter
task, err = idx.DeleteDocumentsByFilter("in_stock = false")
if err != nil {
slog.Error("delete by filter failed", "err", err)
return
}
// Delete all documents
task, err = idx.DeleteAllDocuments()
if err != nil {
slog.Error("delete all failed", "err", err)
return
}
}
Search Operations
Basic Search
func basicSearch(idx meilisearch.IndexManager) {
res, err := idx.Search("mechanical keyboard", &meilisearch.SearchRequest{})
if err != nil {
slog.Error("search failed", "err", err)
return
}
slog.Info("search results", "total", res.EstimatedTotalHits, "hits", len(res.Hits))
for _, hit := range res.Hits {
p := hit.(map[string]interface{})
fmt.Printf(" %s โ $%.2f\n", p["name"], p["price"])
}
}
Filtered Search
Filters use a SQL-like expression syntax. Combine conditions with AND, OR, and parentheses.
func filteredSearch(idx meilisearch.IndexManager) {
res, err := idx.Search("mouse", &meilisearch.SearchRequest{
Filter: "category = Electronics AND price < 30 AND in_stock = true",
Limit: 20,
})
if err != nil {
slog.Error("filtered search failed", "err", err)
return
}
for _, hit := range res.Hits {
p := hit.(map[string]interface{})
fmt.Printf(" %s | $%.2f | %v\n", p["name"], p["price"], p["in_stock"])
}
}
Faceted Search
First declare which attributes are filterable, then request facet distributions in the search call.
func facetedSearch(idx meilisearch.IndexManager) {
// Enable faceting on the index
_, err := idx.UpdateFilterableAttributes(&[]string{"category", "tags", "in_stock"})
if err != nil {
slog.Error("update filterable attrs failed", "err", err)
return
}
res, err := idx.Search("", &meilisearch.SearchRequest{
Facets: []string{"category", "in_stock"},
Limit: 0,
})
if err != nil {
slog.Error("faceted search failed", "err", err)
return
}
if facetDist, ok := res.FacetDistribution.(map[string]interface{}); ok {
for attr, counts := range facetDist {
fmt.Printf("[%s]\n", attr)
for val, count := range counts.(map[string]interface{}) {
fmt.Printf(" %s: %v\n", val, count)
}
}
}
}
Sorting
Define sortable attributes at the index level, then pass Sort in search requests. Multiple sort criteria are applied in order.
func sortedSearch(idx meilisearch.IndexManager) {
// Enable sorting on price
_, err := idx.UpdateSortableAttributes(&[]string{"price", "created_at"})
if err != nil {
slog.Error("update sortable attrs failed", "err", err)
return
}
res, err := idx.Search("", &meilisearch.SearchRequest{
Sort: []string{"price:asc", "created_at:desc"},
Limit: 10,
})
if err != nil {
slog.Error("sorted search failed", "err", err)
return
}
for _, hit := range res.Hits {
p := hit.(map[string]interface{})
fmt.Printf("%s โ $%.2f\n", p["name"], p["price"])
}
}
Search with Highlighting and Crop
func highlightedSearch(idx meilisearch.IndexManager) {
res, err := idx.Search("wireless", &meilisearch.SearchRequest{
AttributesToHighlight: []string{"name", "description"},
AttributesToCrop: []string{"description"},
CropLength: 30,
ShowMatchesPosition: true,
})
if err != nil {
slog.Error("highlight search failed", "err", err)
return
}
for _, hit := range res.Hits {
p := hit.(map[string]interface{})
fmt.Printf("Name: %s\n", p["name"])
if fm, ok := res.FormattedHit(hit).(map[string]interface{}); ok {
fmt.Printf("Highlighted description: %s\n", fm["description"])
}
}
}
Pagination
Meilisearch supports two pagination modes: traditional Offset/Limit and estimated HitsPerPage/Page.
func paginatedSearch(idx meilisearch.IndexManager) {
// Offset-based pagination
res, err := idx.Search("", &meilisearch.SearchRequest{
Offset: 20,
Limit: 10,
})
if err != nil {
slog.Error("paginated search failed", "err", err)
return
}
fmt.Printf("Page 3 of results โ %d total estimated\n", res.EstimatedTotalHits)
}
Index Management
func manageIndexes(client meilisearch.ServiceManager) {
// List all indexes
indexes, err := client.ListIndexes()
if err != nil {
slog.Error("list indexes failed", "err", err)
return
}
for _, idx := range indexes {
fmt.Printf("Index: %s (primary key: %s)\n", idx.UID, idx.PrimaryKey)
}
// Get a single index
idx, err := client.GetIndex("products")
if err != nil {
slog.Error("get index failed", "err", err)
return
}
// Update index (primary key cannot be changed)
idx, err = client.UpdateIndex("products", &meilisearch.IndexConfig{
PrimaryKey: "id",
})
if err != nil {
slog.Error("update index failed", "err", err)
return
}
// Delete index
task, err := client.DeleteIndex("products")
if err != nil {
slog.Error("delete index failed", "err", err)
return
}
client.WaitForTask(task.TaskUID, 5*time.Second)
slog.Info("index deleted")
}
Settings Management
Index settings control how Meilisearch processes fields, ranks results, and handles language. All settings changes return a task uid โ always wait for completion before running dependent operations.
func configureSettings(idx meilisearch.IndexManager) {
// Set which fields are searchable
task, err := idx.UpdateSearchableAttributes(&[]string{"name", "description", "tags"})
if err != nil {
slog.Error("update searchable attrs failed", "err", err)
return
}
client.WaitForTask(task.TaskUID, 10*time.Second)
// Set filterable attributes
task, err = idx.UpdateFilterableAttributes(&[]string{"category", "price", "in_stock", "tags"})
if err != nil {
slog.Error("update filterable attrs failed", "err", err)
return
}
client.WaitForTask(task.TaskUID, 10*time.Second)
// Set sortable attributes
task, err = idx.UpdateSortableAttributes(&[]string{"price", "created_at"})
if err != nil {
slog.Error("update sortable attrs failed", "err", err)
return
}
// Set displayed attributes
task, err = idx.UpdateDisplayedAttributes(&[]string{"id", "name", "price", "category", "in_stock"})
if err != nil {
slog.Error("update displayed attrs failed", "err", err)
return
}
// Set stop words โ commonly filtered out during search
task, err = idx.UpdateStopWords(&[]string{"a", "an", "the", "is", "it", "of", "and"})
if err != nil {
slog.Error("update stop words failed", "err", err)
return
}
// Define synonyms โ groups of words treated as equivalent
synonyms := map[string][]string{
"laptop": {"notebook", "ultrabook"},
"mouse": {"trackpad", "pointer"},
"cheap": {"affordable", "budget", "inexpensive"},
}
task, err = idx.UpdateSynonyms(&synonyms)
if err != nil {
slog.Error("update synonyms failed", "err", err)
return
}
// Customise ranking rules (order matters โ first has highest priority)
rankingRules := []string{
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
"price:desc",
}
task, err = idx.UpdateRankingRules(&rankingRules)
if err != nil {
slog.Error("update ranking rules failed", "err", err)
return
}
// Read current settings
settings, err := idx.GetSettings()
if err != nil {
slog.Error("get settings failed", "err", err)
return
}
slog.Info("current settings", "searchable", settings.SearchableAttributes)
// Reset settings to defaults
task, err = idx.ResetSettings()
if err != nil {
slog.Error("reset settings failed", "err", err)
return
}
}
Task Management
Every write operation in Meilisearch is asynchronous โ the API returns immediately with a task uid, and the actual work happens in a background queue. Always check task status before assuming data is queryable.
func taskLifecycle(client meilisearch.ServiceManager) {
idx := client.Index("products")
task, err := idx.AddDocuments(products)
if err != nil {
slog.Error("add docs failed", "err", err)
return
}
// Option 1: Block with deadline
finished, err := client.WaitForTask(task.TaskUID, 30*time.Second)
if err != nil {
slog.Error("task wait failed", "err", err)
return
}
slog.Info("task completed",
"uid", finished.UID,
"status", finished.Status,
"duration_ms", finished.Duration,
)
// Option 2: Poll manually
for {
info, err := client.GetTask(task.TaskUID)
if err != nil {
slog.Error("get task failed", "err", err)
break
}
if info.Status == meilisearch.TaskStatusSucceeded {
slog.Info("task succeeded", "uid", info.UID)
break
}
if info.Status == meilisearch.TaskStatusFailed {
slog.Error("task failed",
"uid", info.UID,
"error", info.Error.Message,
"code", info.Error.Code,
)
break
}
time.Sleep(50 * time.Millisecond)
}
// Option 3: List recent tasks
tasks, err := client.GetTasks(&meilisearch.TasksQuery{
Limit: 10,
Statuses: []meilisearch.TaskStatus{meilisearch.TaskStatusFailed},
})
if err != nil {
slog.Error("get tasks failed", "err", err)
return
}
slog.Info("recent failed tasks", "count", len(tasks.Results))
}
Advanced Search
Hybrid Search with Vectors (Meilisearch v1.13+)
Enable hybrid search by setting a vector embedding configuration on the index, then providing vector embeddings alongside your documents. Hybrid search combines keyword-based BM25 scoring with vector similarity.
type EmbedDoc struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
_Vectors []float32 `json:"_vectors,omitempty"`
}
func hybridSearch(idx meilisearch.IndexManager) {
// Configure vector search (run once during setup)
embedder := meilisearch.EmbedderConfig{
Source: "userProvided",
Name: "default",
Model: "text-embedding-ada-002",
Distance: "cosine",
}
task, err := idx.UpdateEmbedders(&[]meilisearch.EmbedderConfig{embedder})
if err != nil {
slog.Error("update embedders failed", "err", err)
return
}
client.WaitForTask(task.TaskUID, 30*time.Second)
// Add documents with vector embeddings
// In production, generate embeddings via an external API (OpenAI, etc.)
doc := EmbedDoc{
ID: 101, Title: "Go Concurrency Patterns",
Content: "Goroutines and channels make concurrency in Go expressive...",
_Vectors: []float32{0.012, -0.034, 0.089, 0.215, -0.101}, // truncated example
}
task, err = idx.AddDocuments([]EmbedDoc{doc})
client.WaitForTask(task.TaskUID, 10*time.Second)
// Hybrid search: combine semantic + keyword
res, err := idx.Search("goroutine channels", &meilisearch.SearchRequest{
Hybrid: &meilisearch.Hybrid{
SemanticRatio: 0.7,
},
})
if err != nil {
slog.Error("hybrid search failed", "err", err)
return
}
for _, hit := range res.Hits {
fmt.Printf("Score: %v โ %v\n", hit["_semanticScore"], hit["title"])
}
}
Multi-Index Search and Federated Search
Search across multiple indexes in a single request and optionally merge results.
func federatedSearch(client meilisearch.ServiceManager) {
// Use the multi-search endpoint
multiReq := &meilisearch.MultiSearchRequest{
Queries: []meilisearch.MultiSearchQuery{
{IndexUID: "products", Q: "keyboard", Limit: 5},
{IndexUID: "articles", Q: "keyboard", Limit: 5},
},
Federated: true, // merge and rank across indexes
}
res, err := client.MultiSearch(multiReq)
if err != nil {
slog.Error("multi-index search failed", "err", err)
return
}
slog.Info("federated results", "total", res.EstimatedTotalHits)
for _, hit := range res.Hits {
fmt.Printf("[%s] %v\n", hit.IndexUID, hit.Hit.(map[string]interface{})["name"])
}
}
Error Handling Patterns
Meilisearch errors carry a StatusCode, Code, and Message. Wrap API calls for consistent error handling across your application.
func searchWithErrorHandling(idx meilisearch.IndexManager) {
res, err := idx.Search("mouse", &meilisearch.SearchRequest{})
if err != nil {
// Check if it's a Meilisearch API error
if apiErr, ok := err.(*meilisearch.Error); ok {
switch apiErr.StatusCode {
case 401:
slog.Error("authentication failed โ check API key")
case 404:
slog.Error("index not found โ ensure it exists")
case 429:
slog.Error("rate limited โ reduce request frequency")
default:
slog.Error("meilisearch error",
"code", apiErr.Code,
"message", apiErr.Message,
)
}
} else {
slog.Error("network or client error", "err", err)
}
return
}
// Semantic check for empty results
if res.EstimatedTotalHits == 0 {
slog.Warn("no results found โ consider broadening filters")
}
}
Production Considerations
Connection Pooling and Retry Middleware
Wrap the Meilisearch client with an HTTP transport that pools connections and retries on transient failures.
type retryRoundTripper struct {
next http.RoundTripper
maxRetry int
baseWait time.Duration
}
func (r *retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
for attempt := 0; attempt <= r.maxRetry; attempt++ {
resp, err = r.next.RoundTrip(req)
if err == nil && resp.StatusCode < 500 {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
select {
case <-req.Context().Done():
return nil, req.Context().Err()
case <-time.After(r.baseWait * time.Duration(1<<attempt)):
}
}
return resp, err
}
func newProductionClient() meilisearch.ServiceManager {
baseTransport := &http.Transport{
MaxIdleConns: 256,
MaxIdleConnsPerHost: 64,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
client := &http.Client{
Transport: &retryRoundTripper{
next: baseTransport,
maxRetry: 3,
baseWait: 100 * time.Millisecond,
},
Timeout: 30 * time.Second,
}
cfg := &meilisearch.ClientConfig{
Host: "https://search.example.com",
APIKey: os.Getenv("MEILI_MASTER_KEY"),
HttpClient: client,
}
return meilisearch.NewClient(*cfg)
}
Caching Strategy
Cache frequent searches with an in-memory or Redis-backed layer to reduce Meilisearch load.
var searchCache = ttlcache.New[string, *meilisearch.SearchResponse]()
func cachedSearch(idx meilisearch.IndexManager, query string, opts *meilisearch.SearchRequest) (*meilisearch.SearchResponse, error) {
cacheKey := fmt.Sprintf("%s:%v", query, opts)
if cached, ok := searchCache.Get(cacheKey); ok {
return cached, nil
}
res, err := idx.Search(query, opts)
if err != nil {
return nil, err
}
searchCache.Set(cacheKey, res, 30*time.Second)
return res, nil
}
Metrics Collection
Export Meilisearch query latency and error counts to Prometheus for operational visibility.
var (
searchDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "meilisearch_search_duration_ms",
Help: "Latency of Meilisearch searches",
Buckets: []float64{5, 10, 25, 50, 100, 250, 500, 1000},
}, []string{"index"})
searchErrors = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "meilisearch_search_errors_total",
Help: "Total Meilisearch search errors",
}, []string{"index", "error_code"})
)
func instrumentedSearch(idx meilisearch.IndexManager, query string, opts *meilisearch.SearchRequest) (*meilisearch.SearchResponse, error) {
start := time.Now()
res, err := idx.Search(query, opts)
elapsed := time.Since(start).Milliseconds()
if err != nil {
searchErrors.WithLabelValues(idx.UID, "search_error").Inc()
return nil, err
}
searchDuration.WithLabelValues(idx.UID).Observe(float64(elapsed))
return res, nil
}
Client API Reference Table
| Method | Description |
|---|---|
New(host, opts) |
Create a new client with host URL and optional config |
NewClient(cfg) |
Create client from a ClientConfig struct |
Client.Health() |
Check server health and version |
Client.CreateIndex(cfg) |
Create a new index with primary key |
Client.GetIndex(uid) |
Fetch metadata for an existing index |
Client.ListIndexes() |
List all indexes |
Client.UpdateIndex(uid, cfg) |
Update index settings |
Client.DeleteIndex(uid) |
Delete an index |
Client.Index(uid) |
Get an IndexManager for the given uid |
Index.AddDocuments(docs) |
Add or replace documents |
Index.UpdateDocuments(docs) |
Partial update on existing documents |
Index.DeleteDocument(id) |
Delete a single document by primary key |
Index.DeleteDocuments(ids) |
Delete multiple documents by primary key |
Index.DeleteDocumentsByFilter(f) |
Delete documents matching a filter |
Index.DeleteAllDocuments() |
Remove all documents from the index |
Index.Search(query, req) |
Perform a search with optional filters, facets, sort, etc. |
Index.GetDocument(id) |
Retrieve a single document |
Index.GetDocuments(req) |
List documents with optional pagination |
Index.GetSettings() |
Read all index settings |
Index.UpdateSearchableAttributes(v) |
Set which fields are searchable |
Index.UpdateFilterableAttributes(v) |
Set fields usable in filter expressions |
Index.UpdateSortableAttributes(v) |
Set fields usable in sort |
Index.UpdateDisplayedAttributes(v) |
Restrict fields returned in search results |
Index.UpdateStopWords(v) |
Set words to ignore during search |
Index.UpdateSynonyms(v) |
Define word synonym groups |
Index.UpdateRankingRules(v) |
Customize ranking criteria |
Index.UpdateEmbedders(v) |
Configure vector search (v1.13+) |
Client.GetTask(uid) |
Fetch a single task by its uid |
Client.GetTasks(query) |
List tasks with optional filtering |
Client.WaitForTask(uid, timeout) |
Block until a task completes |
Client.MultiSearch(req) |
Search multiple indexes in one request |
Comparison With Other Go Search Libraries
| Feature | Meilisearch | Bleve | Zinc (zincsearch) |
|---|---|---|---|
| Engine type | RESTful server (Rust) | Embedded Go library | RESTful server (Go) |
| Typo tolerance | Built-in, auto | Manual via fuzzy queries | Built-in |
| Faceting | Native with distributions | Custom aggregation | Native |
| Vector/hybrid search | Supported (v1.13+) | Not supported | Not supported |
| Deployment | Self-hosted or cloud | In-process | Self-hosted or cloud |
| Index persistence | Automatic (disk) | Requires manual indexing config | Automatic (disk) |
| Go client | Official maintained client | Native Go API | Official client |
| Learning curve | Low | Medium | Low-medium |
| Best for | Fast standalone search | Embedded search in Go apps | Elasticsearch replacement |
When to choose Meilisearch: you need a fast, standalone search service with minimal configuration and features like typo tolerance and faceting out of the box.
When to choose Bleve: you want an embedded search engine that runs in the same process as your Go application with no external dependencies.
When to choose Zinc: you need an Elasticsearch-compatible API with a lightweight footprint but want to stay within the Go ecosystem.
Migration Patterns from Other Search Engines
From Elasticsearch
- Replace
_sourceinclusion/exclusion withdisplayedAttributes. - Replace
multi_matchwith Meilisearch’s default cross-field search. - Replace
term/match/fuzzywith plain text queries (typo tolerance is automatic).
From Bleve
- Move index storage from embedded BoltDB/Forestdb path to Meilisearch server.
- Change document indexing from
index.Index()toidx.AddDocuments(). - Replace
query.NewMatchQuery()with Meilisearch search strings andfilterexpressions.
// Bleve equivalent to Meilisearch
// Bleve: query.NewTermQuery("electronics") -> searcher
// Meilisearch: idx.Search("", &meilisearch.SearchRequest{Filter: "category = Electronics"})
Conclusion
Go and Meilisearch form a powerful combination for adding fast, typo-tolerant search to any application. The Go client covers the full Meilisearch API โ documents, search, settings, tasks, and vector search โ with clear abstractions. Start with the basic client and CRUD examples, add settings tuning as your search requirements grow, and layer in production patterns like caching, retries, and metrics when you deploy.
Resources
- Meilisearch Go Client GitHub
- Meilisearch Documentation
- Meilisearch Cloud
- Official Meilisearch Blog
- Bleve โ Full-text search in Go
- ZincSearch โ Lightweight Elasticsearch alternative in Go
Comments