» Go: Building Full-Text Search API with ElasticSearch » 3. Search Documents » 3.1 Send Search Requests

Send Search Requests

Update domain/gateway/book_manager.go:

@@ -12,4 +12,5 @@ import (
 // BookManager manages all books
 type BookManager interface {
        IndexBook(ctx context.Context, b *model.Book) (string, error)
+       SearchBooks(ctx context.Context, query string) ([]*model.Book, error)
 }

Update infrastructure/search/es.go:

@@ -5,8 +5,11 @@ package search
 
 import (
        "context"
+       "encoding/json"
 
        "github.com/elastic/go-elasticsearch/v8"
+       "github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
+       "github.com/elastic/go-elasticsearch/v8/typedapi/types"
 
        "literank.com/fulltext-books/domain/model"
 )
@@ -42,3 +45,29 @@ func (s *ElasticSearchEngine) IndexBook(ctx context.Context, b *model.Book) (str
        }
        return resp.Id_, nil
 }
+
+// SearchBooks search from ES and return a list of books
+func (s *ElasticSearchEngine) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+       resp, err := s.client.Search().Index(INDEX_BOOK).
+               Request(&search.Request{
+                       Query: &types.Query{
+                               MultiMatch: &types.MultiMatchQuery{
+                                       Query:  query,
+                                       Fields: []string{"title", "author", "content"},
+                               },
+                       },
+               }).
+               Do(ctx)
+       if err != nil {
+               return nil, err
+       }
+       books := make([]*model.Book, 0)
+       for _, hit := range resp.Hits.Hits {
+               var b model.Book
+               if err := json.Unmarshal(hit.Source_, &b); err != nil {
+                       return nil, err
+               }
+               books = append(books, &b)
+       }
+       return books, nil
+}

Here we use MultiMatch to query on mutilple fields “title“, “author“ and “content“.

Update application/executor/book_operator.go:

@@ -24,3 +24,7 @@ func NewBookOperator(b gateway.BookManager) *BookOperator {
 func (o *BookOperator) CreateBook(ctx context.Context, b *model.Book) (string, error) {
        return o.bookManager.IndexBook(ctx, b)
 }
+
+func (o *BookOperator) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+       return o.bookManager.SearchBooks(ctx, query)
+}

Update adapter/router.go:

@@ -14,6 +14,8 @@ import (
        "literank.com/fulltext-books/domain/model"
 )
 
+const fieldQuery = "q"
+
 // RestHandler handles all restful requests
 type RestHandler struct {
        bookOperator *executor.BookOperator
@@ -31,6 +33,7 @@ func MakeRouter(wireHelper *application.WireHelper) (*gin.Engine, error) {
        // Create a new Gin router
        r := gin.Default()
 
+       r.GET("/books", rest.searchBooks)
        r.POST("/books", rest.createBook)
        return r, nil
 }
@@ -51,3 +54,13 @@ func (r *RestHandler) createBook(c *gin.Context) {
        }
        c.JSON(http.StatusCreated, gin.H{"id": bookID})
 }
+
+func (r *RestHandler) searchBooks(c *gin.Context) {
+       books, err := r.bookOperator.SearchBooks(c, c.Query(fieldQuery))
+       if err != nil {
+               fmt.Printf("Failed to search books: %v\n", err)
+               c.JSON(http.StatusNotFound, gin.H{"error": "failed to search books"})
+               return
+       }
+       c.JSON(http.StatusOK, books)
+}

Run the server again and try it with curl:

curl 'http://localhost:8080/books?q=katniss+hunger'

Example response:

[
  {
    "title": "The Hunger Games",
    "author": "Suzanne Collins",
    "published_at": "2008-09-14",
    "content": "In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."
  }
]

Try it in tools like Postman or Insomnia:

http://localhost:8080/books?q=new%20york%20circus%20girl

In URL encoding, both %20 and + are used for representing spaces, but they serve slightly different purposes depending on the context.

Insomnia Screenshot

Great! You just completed a full-text search.

Note:
Full text search requires language analyzers. The default analyzer is the standard analyzer, which may not be the best especially for Chinese, Japanese, or Korean text. With language specific analyzers, you can get better search experience.

PrevNext