👈 Back

Go and HTMX

Implementing Search Functionality

By Keith Thomson 12 min read go,automation,webdev

🔬Implementing Search with HTMX

This guide provides the code to implement a search functionality for your blog using HTMX.

🧭 Search Logic

Create a new file services/search_posts.go and add the following code. This file will contain the logic for searching posts.

package services

import (

	"encoding/json"

	"io"

	"os"

	"strings"

	"github.com/whaler/templ-go/models"

)

func SearchPosts(query string) ([]models.Post, error) {

	file, err := os.Open("allposts.json")

	if err != nil {

		return nil, err

	}

	defer file.Close()

	bytes, err := io.ReadAll(file)

	if err != nil {

		return nil, err

	}

	var posts []models.Post

	if err := json.Unmarshal(bytes, &posts); err != nil {

		return nil, err

	}

	if query == "" {

		return posts, nil

	}

	var filteredPosts []models.Post

	for _, post := range posts {

		if strings.Contains(strings.ToLower(post.Title), strings.ToLower(query)) ||

			strings.Contains(strings.ToLower(post.Subtitle), strings.ToLower(query)) ||

			strings.Contains(strings.ToLower(post.Summary), strings.ToLower(query)) {

			filteredPosts = append(filteredPosts, post)

		}

	}

	return filteredPosts, nil

}

🔍 Search Handler

Create a new file handlers/handle_search.go and add the following code. This handler will process the search requests.

package handlers

import (

	"net/http"

	"github.com/whaler/templ-go/services"

	"github.com/whaler/templ-go/templates"

)

func HandleSearchPosts(w http.ResponseWriter, r *http.Request) {

	query := r.FormValue("query")

	posts, err := services.SearchPosts(query)

	if err != nil {

		http.Error(w, "Failed to search posts", http.StatusInternalServerError)

		return

	}

	// We are only rendering the post cards, not the whole page

	component := templates.PostCardList(posts)

	component.Render(r.Context(), w)

}

🗺️ Add the Search Route

Modify your main.go file to add the new /search route. You will need to convert your app to use net/http handlers, or use an adapter. Given you are using fiber, I will show how to adapt the net/http handler.

First, you need to get the adaptor by running this command:

go get github.com/gofiber/adaptor/v2

Then, in your main function, add the following route:

app.Post("/search", adaptor.HTTPHandlerFunc(handlers.HandleSearchPosts))

So your main.go will look something like this (showing only the relevant part):

package main

import (

	"log"

	"sort"

	"github.com/gofiber/fiber/v2"

	"github.com/gofiber/adaptor/v2"

	"github.com/whalerapi/templ-go/handlers"

	"github.com/whalerapi/templ-go/models"

	"github.com/whalerapi/templ-go/services"

	"github.com/whalerapi/templ-go/templates"

)

// ... (rest of your main.go file)

func main() {

	// ... (your existing code)

	app.Get("/about", handlers.AboutHandler)

	app.Get("/all_posts_page", handlers.HandleAllPosts)

	app.Get("/blog/:slug", handlers.HandlePost)

	app.Get("/resume", handlers.ResumeHandler(models.MyProfile))

	app.Post("/search", adaptor.HTTPHandlerFunc(handlers.HandleSearchPosts)) // Add this line

	log.Println("Listening on :8080")

	if err := app.Listen(":8080"); err != nil {

		log.Fatalf("failed to start server: %v", err)

	}

}

4. Update the Frontend

Modify your templates/all_posts_page.templ file to add the search bar with HTMX attributes.

Replace this:

<div class="mb-8">

	<input type="text" placeholder="Search posts..." class="border border-gray-300 rounded-lg py-2 px-4 w-full"/>

</div>

With this:

<div class="mb-8">

	<input type="text" name="query" placeholder="Search posts..."

		class="border border-gray-300 rounded-lg py-2 px-4 w-full"

		hx-post="/search"

		hx-trigger="keyup changed delay:500ms"

		hx-target="#search-results"

		hx-swap="innerHTML"

		hx-indicator=".htmx-indicator"

	/>

	<span class="htmx-indicator">Searching...</span>

</div>

Then, wrap the grid of posts in a div with the id="search-results".

Replace this:

<div class="grid grid-cols-1 md:grid-cols-2 gap-8">

	for _, post := range posts {

		@PostCard(post)

	}

</div>

With this:

<div id="search-results" class="grid grid-cols-1 md:grid-cols-2 gap-8">

	for _, post := range posts {

		@PostCard(post)

	}

</div>

Finally, you need to make sure you have HTMX included in your project. You can add this to your main layout file (templates/layout.templ) in the <head> section:

<script src="https://unpkg.com/htmx.org@1.9.10"></script>

That's it! After making these changes, you will have a working search functionality on your "All Posts" page.