Building a Counter App with htmx, Go Fiber, and elem-go
Tags: go, htmx, webdev, tutorial
Published: 2023-10-06 21:06:03


In this tutorial, we'll build a simple counter application using Go Fiber, htmx, and the `elem-go` library for type-safe HTML templating.

## Explaining the Dependencies

We'll use three primary packages to build the app:

- **[Go Fiber](https://github.com/gofiber/fiber):** An HTTP web server implementation inspired by Express.js's design in Node.js. Go Fiber facilitates the development of speedy and scalable web applications in Go.
- **[htmx](https://htmx.org/):** Provides direct access to AJAX, WebSockets, and Server-Sent Events in HTML, simplifying dynamic updates of web pages without full reloads.
- **[elem-go](https://github.com/chasefleming/elem-go):** A nimble Go library designed for programmatic and type-safe HTML element creation and management. It ensures you're working with valid HTML elements, attributes, and styles, helping to minimize potential runtime errors.

## Setting Up the Project

Start by initializing a new Go project:

```bash
mkdir go-htmx-counter
cd go-htmx-counter
go mod init github.com/yourusername/go-htmx-counter
```

Next, install the required packages:

```bash
go get github.com/chasefleming/elem-go
go get github.com/gofiber/fiber/v2
```

## Setting Up the Go Fiber Server

Create a new file named `main.go`. Here, set up a root route using Go Fiber, which listens on port 3000 and serves the "Hello, World!" message:

```go
package main

import (
	"github.com/gofiber/fiber/v2"
)

func main() {
	app := fiber.New()

	app.Get("/", func(c *fiber.Ctx) error {
		c.Type("html")
		return c.SendString("Hello, world!")
	})

	app.Listen(":3000")
}
```

To start your server:

```bash
go run main.go
```

Navigate to `http://localhost:3000`, and you should be greeted by "Hello, world!".

## Importing Subpackages from `elem-go`

Once you have installed the `elem-go` package, you also gain access to its subpackages, which are designed to enhance your development experience with type-safe utilities for attributes, styles, and htmx integration. Here’s how you can import them into your project:

```go
import (
    //...[existing imports]...
	"github.com/chasefleming/elem-go/attrs"
	"github.com/chasefleming/elem-go/styles"
	"github.com/chasefleming/elem-go/htmx"
)
```

Make sure to include these imports in your `main.go` file or any other file where you are using the `elem-go` library. By importing these subpackages, you gain access to a suite of constants and helper functions that facilitate the creation of type-safe HTML elements. This structure helps prevent common mistakes such as typos in attribute names and ensures that style properties are correctly applied to your elements.

Each subpackage serves a specific purpose:

- **`attrs`**: This subpackage contains constants for HTML attribute names, ensuring that you use valid attribute keys when creating elements.
- **`styles`**: It provides constants for CSS property names, allowing you to apply styles with the correct property keys.
- **`htmx`**: This subpackage includes constants for htmx-specific attributes, making it easier to work with htmx in your templates.

## Create the Counter Page using `elem-go`

Begin by adding the script tag for the `htmx` source to our app's `<head>`. With `elem-go`, you can generate this HTML in a type-safe manner. Using `elem.` followed by the tag name lets you create the desired elements. The first argument in an `elem-go` element defines its attributes, while subsequent arguments denote child elements:

```go
app.Get("/", func(c *fiber.Ctx) error {
	head := elem.Head(nil, elem.Script(attrs.Props{attrs.Src: "https://unpkg.com/htmx.org@1.9.6"}))
	//...[rest of the code]...
})

```

With `elem-go`, you can also utilize type-safe constants for both attribute and style keys. To define the styles, add the following after `head`:

```go
bodyStyle := styles.Props{  
    styles.BackgroundColor: "#f4f4f4",
    styles.FontFamily:      "Arial, sans-serif",
    styles.Height:          "100vh",
    styles.Display:         "flex",
    styles.FlexDirection:   "column",
    styles.AlignItems:      "center",
    styles.JustifyContent:  "center",
}

buttonStyle := styles.Props{
    styles.Padding:       "10px 20px",
    styles.BackgroundColor: "#007BFF",
    styles.Color:         "#fff",
    styles.BorderColor:   "#007BFF",
    styles.BorderRadius:  "5px",
    styles.Margin:        "10px",
    styles.Cursor:        "pointer",
}
```

After defining the styles, we'll construct our body elements using the methods provided by the `elem-go` library. Additionally, the `htmx` subpackage we imported offers specific values, which are accessible with the `htmx.` prefix:

```go
body := elem.Body(
    attrs.Props{
        attrs.Style: bodyStyle.ToInline(),
    },
    elem.H1(nil, elem.Text("Counter App")),
    elem.Div(attrs.Props{attrs.ID: "count"}, elem.Text("0")),
    elem.Button(
        attrs.Props{
            htmx.HXPost:   "/increment",
            htmx.HXTarget: "#count",
            attrs.Style:   buttonStyle.ToInline(),
        }, 
        elem.Text("+")
    ),
    elem.Button(
        attrs.Props{
            htmx.HXPost:   "/decrement",
            htmx.HXTarget: "#count",
            attrs.Style:   buttonStyle.ToInline(),
        }, 
        elem.Text("-")
    ),
)
```

Once the body content is defined, encapsulate it within the `<html>` tag using the `elem.Html` method followed by `Render`. Append this after defining the `body`:

```go
pageContent := elem.Html(nil, head, body)  
  
html := pageContent.Render()
```

## Adding Interactivity with htmx

Next, we'll establish handlers for the `/increment` and `/decrement` endpoints. These are invoked via `htmx.HXPost`, the programmatic equivalent of the `hx-post` attribute in HTML. To do this, integrate the following into your `main` function after initializing the `app`:

```go
var count int

app.Post("/increment", func(c *fiber.Ctx) error {
    count++
    return c.SendString(fmt.Sprintf("%d", count))
})

app.Post("/decrement", func(c *fiber.Ctx) error {
    count--
    return c.SendString(fmt.Sprintf("%d", count))
})
```

The above routes will update and then send back the current value of the counter. Ensure you have imported the `fmt` package at the top of your file to use the `Sprintf` function.

## Running the Counter App

Run the server, navigate to `http://localhost:3000`, and you'll be greeted with the Counter App. The "+" and "-" buttons will now seamlessly update the count value without a full page refresh.

Congratulations on successfully building a basic counter app using Go Fiber for the backend, htmx for frontend interactivity, and `elem-go` for type-safe HTML templating. Expand on this foundation for more advanced projects!

If you'd like to see the full code, [check out this repo](https://github.com/chasefleming/elem-go/blob/main/examples/htmx-fiber-counter/main.go).