Golang + htmx + Tailwind CSS: Create a Responsive Web Application

A guide to implementing web app without using Javascript

·

5 min read

Golang + htmx + Tailwind CSS: Create a Responsive Web Application

Background

In today’s web development landscape, JavaScript has long been the language of choice for creating dynamic and interactive web applications.

As a Go developer, what if you don’t want to use Javascript and still implement a responsive web application?

Imagine a sleek to-do list app that updates instantly as you check off tasks without a full-page reload. This is the power of Golang and htmx!

Combining Go and htmx allows us to create responsive and interactive web applications without writing a single line of JavaScript.

In this blog, we will explore how to use htmx and Golang to build web applications. (It can be used with other your favorite platforms, too.)

As a learning, we will implement basic create and delete operations for users.

What is htmx?

htmx is a modern HTML extension that adds bidirectional communication between the browser and the server.

It allows us to create dynamic web pages without writing JavaScript, as it provides access to AJAX, server-sent events, etc in HTML directly.

How htmx works?

  • When a user interacts with an element that has an htmx attribute (e.g., clicks a button), the browser triggers the specified event.

  • htmx intercepts the event and sends an HTTP request to the server-side endpoint specified in the attribute (e.g., hx-get="/my-endpoint").

  • The server-side endpoint processes the request and generates an HTML response.

  • htmx receives the response and updates the DOM according to the hx-target and hx-swap attributes. This can involve:

 — Replacing the entire element’s content.
 — Inserting new content before or after the element.
 — Appending content to the end of the element.

Let’s understand it in more depth with an example.

<button hx-get="/fetch-data" hx-target="#data-container">
   Fetch Data
</button>
<div id="data-container"></div>

In the above code, when the button is clicked:

  1. htmx sends a GET request to /fetch-data.

  2. The server-side endpoint fetches data and renders it as HTML.

  3. The response is inserted into the #data-container element.

Create and delete the user

Below are the required tools/frameworks to build this basic app.

  • Gin (Go framework)

  • Tailwind CSS

  • htmx

Basic setup

  • Create main.go file at the root directory.

main.go

package main

import (
 "fmt"
 "github.com/gin-gonic/gin"
)

func main() {
 router := gin.Default()

 router.Run(":8080")
 fmt.Println("Server is running on port 8080")
}

It sets up a basic Go server, running at port 8080.
Run go run main.go to run the application.

  • Create a HTML file at the root directory, to render the user list.

users.html

<!DOCTYPE html>
<html>
   <head>
      <title>Go + htmx app </title>
      <script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"></script>
      <script src="https://cdn.tailwindcss.com"></script>
   </head>
   <body class="text-center flex flex-col w-full gap-6 mt-10">
      <table id="user-list" class="w-1/2 mx-auto mt-4 border border-gray-300">
         <thead>
            <tr class="border border-gray-300">
               <th class="px-4 py-2">Name</th>
               <th class="px-4 py-2">Email</th>
               <th class="px-4 py-2">Actions</th>
            </tr>
         </thead>
         <tbody>
            {{ range .users }}
            <tr class="border border-gray-300">
               <td class="px-4 py-2">{{ .Name }}</td>
               <td class="px-4 py-2">{{ .Email }}</td>
               <td class="px-4 py-2">
                  <button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded">Delete</button>
               </td>
            </tr>
            {{ end }}
         </tbody>
      </table>
   </body>
</html>

We have included,

htmx using the script tag — https://unpkg.com/htmx.org@2.0.0

Tailwind CSS with cdn link —
https://cdn.tailwindcss.com

Now, we can use Tailwind CSS classes and render the templates with htmx.

As we see in users.html, we need to pass users array to the template, so that it can render the users list.

For that let’s create a hardcoded static list of users and create a route to render users.html .

Fetch users

main.go

package main

import (
 "fmt"
 "net/http"
 "text/template"

 "github.com/gin-gonic/gin"
)

func main() {
 router := gin.Default()

 router.GET("/", func(c *gin.Context) {
  users := GetUsers()

  tmpl := template.Must(template.ParseFiles("users.html"))
  err := tmpl.Execute(c.Writer, gin.H{"users": users})
    if err != nil {
       panic(err)
    }
 })

 router.Run(":8080")
 fmt.Println("Server is running on port 8080")
}

type User struct {
 Name  string
 Email string
}

func GetUsers() []User {
 return []User{
  {Name: "John Doe", Email: "johndoe@example.com"},
  {Name: "Alice Smith", Email: "alicesmith@example.com"},
 }
}

We have added a route / to render the user list and provide a static list of users (to which we will add new users ahead).

That’s all. Restart the server and let’s visit — localhost:8080 to check whether it renders the user list or not. It will render the user list as below.

Rendered users list

Create user

Create file user_row.html. It will be responsible for adding a new user row to the user table.

user_row.html

<tr class="border border-gray-300">
    <td class="px-4 py-2">{{ .Name }}</td>
    <td class="px-4 py-2">{{ .Email }}</td>
    <td class="px-4 py-2">
        <button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded">Delete</button>
    </td>
</tr>

P.S. — You will notice that it has the same structure as the user table row in users.html . That’s because we want the same styling for the new row we are going to add.

  • Modify users.html. Add the Below code above the <table></table> tags.
<form hx-post="/users" hx-target="#user-list" hx-swap="beforeend">
   <input type="text" name="name" placeholder="Name" class="border border-gray-300 p-2 rounded">
   <input type="email" name="email" placeholder="Email" class="border border-gray-300 p-2 rounded">
   <button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Add User</button>
</form>
  • hx-post=“/users” — When the form will be submitted, it will trigger post request to /users route.

  • hx-target=“#user-list” —Specify the target where we want to add data.

  • hx-swap=“beforeend”  — Specify the position where want to add data. In our case we want to add new user at the end of list, so we have used beforeend value.

Let’s implement /users(POST) route in the main.go file.

router.POST("/users", func(c *gin.Context) {
  tmpl := template.Must(template.ParseFiles("user_row.html"))

  name := c.PostForm("name")
  email := c.PostForm("email")

  user := User{Name: name, Email: email}
  err := tmpl.Execute(c.Writer, user)
  if err != nil {
   panic(err)
  }
})

It takes the name and email from the form input and executes the user_row.html.

Let’s try to add a new user to the table. Visit localhost:8080 and click the Add User button.

Add new user to the list

Yayy! We’ve successfully added a new user to the list 🎉.

To dive deeper into the detail implementation guide, check out the complete guide at Canopas.


If you like what you read, be sure to hit 💖 button! — as a writer it means the world!

I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.

Happy coding!👋

Did you find this article valuable?

Support Canopas's blog by becoming a sponsor. Any amount is appreciated!