Skip to content

Instantly share code, notes, and snippets.

@niyabits
Created October 21, 2024 10:10
Show Gist options
  • Save niyabits/914d7010d528703fb6af8fab899059fe to your computer and use it in GitHub Desktop.
Save niyabits/914d7010d528703fb6af8fab899059fe to your computer and use it in GitHub Desktop.
HTMX, Templ and Go Setup

Setting up HTMX and Templ for Go

Motivation

Sites built with JavaScript frameworks like Next.js are often complicated and rely a lot on JavaScript for even primitive tasks like submitting form, state management, hydration etc.

Modern sites built with React tend to use JavaScript as a holistic language for serving, rendering, and routing in a website. The problem with this approach is that if you have your business logic in a different programming language you need to create more abstractions and APIs to use it within JavaScript or write the business logic in Node.js.

HTMX

HTMX is a small (~14k minified and gzipped), dependency-free JavaScript library, it doesn't care about which language you use for your backend or business logic, which is a great thing, because now we can use our existing business logic code in Go to make a website.

You can use a Go templating language to generate HTML markup and use HTMX to put it in your website.

Templ and Go

Templ is a tool which can be used to build HTML with Go. It includes templating-language like features but can really do more than that like — Server-side rendering, static rendering, use Go etc.

You can create a templ component by creating a .templ file.

package main  
  
templ hello(name string) {  
	<div>Hello, { name }</div>  
}

Templ also comes with a CLI that can be used to convert the .templ to a Go file which can be served using any http library.

Setup

Setting up a Go web Server

  1. Create a new Go project:
mkdir hello-world
cd hello-world
go mod init github.com/<username>/<repository>
  1. Let's use the Go's standard net/http library to create a simple web server:
// main.go
package main  
  
import (  
	"fmt"  
	"net/http"  
)  
  
func main() {  
	http.Handle("/", func(w http.ResponseWriter, _ *http.Request) {
		io.WriteString(w, "Hello World! \n")
	}) 
	  
	fmt.Println("Listening on :3000")  
	http.ListenAndServe(":3000", nil)  
}

Setting up Templ

  1. To install the templ CLI you can simply use go install:
go install github.com/a-h/templ/cmd/templ@latest
  1. To Install templ in your project run:
go get github.com/a-h/templ
  1. Create a templ file hello.templ in the same directory as main.go
// hello.templ
package main  
  
templ hello(name string) {  
	<div>Hello, { name }</div>  
}
  1. Generate Go code
templ generate

The generate commands creates a hello_templ.go files which has Go code. The Go code will have a function called hello that can be called with a name argument.

  1. Use templ with the web server Import templ and replace the existing handler function with a templ.Handler function
// main.go
// ...
import (  
// ...
	"github.com/a-h/templ"  
)

func main() {  
	component := hello("Templ!")  
	http.Handle("/", templ.Handler(component))  
	  
	fmt.Println("Listening on :3000")  
	http.ListenAndServe(":3000", nil)  
}

Setting up HTMX

  1. We first start by creating a Layout.templ file in the root directory:
// Layout.templ
package main

templ Layout() {
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Title</title>
    </head>
    <body>
        { children... }
    </body>
    </html>
}

All of our pages will be wrapped by the Layout component. We can use the template composition syntax in templ to achieve this. Notice the { children... }.

  1. The recommended way to setup HTMX is to download a copy of minified HTMX from unpkg.com and include it in a script tag in the head of the Layout.templ file:
<!-- Layout.templ -->
	<!-- ... -->
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Title</title>
		<!-- HTMX -->
		<script src="../lib/htmx.min.js"></script>
    </head>
	<!-- ... -->

Setting up Tailwind

  1. We need to install and configure the tailwind compiler
npm install -D tailwindcss
npx tailwindcss init
  1. Configure tailwind for templ
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./**/*.{templ}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

Make sure to specify the path where your templ files are located.

  1. Create an input.css file in the root directory and add:
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Start the Tailwind CLI build process:
yarn tailwindcss -i input.css -o static/css/tw.css --watch
  1. Add a CSS file to your layout.templ
<!-- Layout.templ -->
	<!-- ... -->
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
		<link rel="stylesheet" href="/static/css/tw.css"/>
        <title>Title</title>
		<!-- HTMX -->
		<script src="../lib/htmx.min.js"></script>
    </head>
	<!-- ... -->

We now have Templ, HTMX, and tailwind setup ready to use.

Development Setup

The setup works but is quite tedious to use at this point, it requires us to restart the Go server and re-run templ generate every time we make a change in the Go code or the templ code. We also need to refresh the browser every time to see the change.

Watch Mode for Go

There are a few options for watching file changes and re-running Go automatically like air and gow. Both of them are excellent tool and works for us. Let's use gow to watch for changes and restart Go server:

gow run main.go -e go,mod,html,css

Watch Mode for Templ

Templ's command line tool comes in with a built-in watcher which recompiles the templ files to Go everytime there is a change:

templ generate -watch --include-timestamp false --include-version false

Live Reloading

We don't need to re-run go or templ anymore but we still need to refresh the browser every time there is a change, we can use the templ's command line tool's proxy for hot reloading the browser.

templ generate -watch -include-timestamp false -include-version false -proxy="http://localhost:3000" -proxyport="8080" -open-browser=false -cmd="go run main.go"

This command will start a proxy server to run on the port 8080. You can visit localhost:8080 for development.

Learnings from setting up

  • There are no meta-frameworks
  • Features like hot-live reload have to be manually setup
  • The setup can be challenging compared to JS out of box meta frameworks
  • The setup can be unreliable sometimes are require manual restarts

Surprises on the way

  • Poor MDX support
  • example - Resolving Filesystem request resolution needs a custom solution
  • Go learning curve for me personally, not being aware of std lib

Next steps, or what you want to explore

  • State and interactivity Alpine.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment