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: 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: Provides direct access to AJAX, WebSockets, and Server-Sent Events in HTML, simplifying dynamic updates of web pages without full reloads.
- 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:
mkdir go-htmx-counter
cd go-htmx-counter
go mod init github.com/yourusername/go-htmx-counter
Next, install the required packages:
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:
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:
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:
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:
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
:
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:
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
:
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
:
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.
Top comments (1)
Nice work Chase.
github.com/anycable/anycable-go looks like a nice match with go-elem. It's designed for Turbo, but it will work with go-elem and htmx and SSE / Web Sockets.
It can also embed the NATS Server for you.