Today there is a plethora of web frameworks that use client-side javascript to create reactive websites. It has become quite difficult to choose one of them, they all look amazing and offer so many features. But in a way they hide things from you, you don’t really know how it all fits together. So let us see what else we can use to build a web application. We will go back to the roots and introduce TEMPL, a Go package developed by Adrian Hesketh that goes beyond the standard html/template library. It’s useful to create simple static web components that can be integrated into a bigger picture and served by a compiled binary. Assembling small components yourself helps to keep control of your code.
Installation
Installing a package in Go is as simple as
go install github.com/a-h/templ/cmd/templ@latest
A small templ
example
Traditionaly let start by initializing a new Go module and fetching templ
mkdir welcome
cd welcome
# create a go.mod file to manage dependencies
# update below the unique identifier of your module
go mod init github.com/<your_handle>/welcome
# download and install templ
go get github.com/a-h/templ
Create your first template
vi welcome.templ
package main
templ welcome(firstname string) {
<h1>Welcome to yet emerging technologies, { firstname } !</h1>
}
You can now process your template to generate Go code from it
templ generate
To watch current directory for changes and regenerate constantly
templ generate -watch
But code built like that isn’t optimized for production use
You should now have a welcome_templ.go
file containing your newly generated Go function named welcome
with the following signature, it returns templ.Component
an interface that has a Render
method on it that is used to render the component to an io.Writer
.
func welcome(firstname string) templ.Component {
returnedtempl.Component
can be called like that
component := welcome("Sebastien")
to generate the expected content wherever you want for example on Stdout
component.Render(context.Background(), os.Stdout)
So it looks like that
package main
import (
"context"
"os"
)
func main() {
component := welcome("Sebastien")
component.Render(context.Background(), os.Stdout)
}
When you compile and run it you should see
go run .
<h1>Welcome to yet emerging technologies, sebastien</h1>
Writing HTML snippets on Stdout
isn’t a dream job so let’s instead serve it thru HTTP using the net/http
package
package main
import (
"fmt"
"net/http"
"github.com/a-h/templ"
)
func main() {
component := welcome("sebastien")
http.Handle("/", templ.Handler(component))
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}
Test it to see if everything looks good ?
curl http://localhost:3000
<div>Welcome to yet emerging technologies, sebastien</div>
Our Hello world works great! As you’ve seen, templ
generates functions that output your snippets. Remember that you can benefit from the power of Go in your template (if
, switch
, etc…).
templ
files can also contain call to other components or Go code to implement some logic processing outside the templ
declaration which will be considered as Go ordinary code.
So to summarize, components are markup and Go code that is compiled into Go functions that return a templ.Component
interface by running the templ generate
command.
Syntax cheatsheet
- All Tags must be close:
<br/>
,<div></div>
,<button></button>
strings
values are automatically escaped (security measure)&
characters in the URL are escaped to&
."
characters are escaped to"
.'
characters are escaped to'
.
- It’s possible to use function calls in components string attribute expressions
Below you’ll find a cheatsheet of what can be used within the templ
declaration.
tag | description |
---|---|
{ variable } |
replaced by received value for variable |
@component |
call a component or inject the rendered output of a component h |
function call |
need to return string or (string, error) |
?={ true | false } |
only output preceding attribute if true |
if/else |
reserved keywords, Capitalize or put in String to avoid statement |
switch |
reserved keywords, Capitalize or put in String to avoid statement |
for |
reserved keywords, Capitalize or put in String to avoid statement |
{{ ... }} |
raw go to be excuted |
Web Application Structure
Hopefully you should now have a better understanding of the usefulness of this templating engine, let’s share what is the recommended structure for a web app using this module. Counter, available in the repository is a good example of that.
- All of you components should live in their own
components
package. - Database related interactions are also in their own
db
package - Same for your
handlers
,services
used by your handlers andsession
which implement HTTP session IDs
Finally a main.go
will glue all of that together. So the directory structure should look something like that
components/
db/
handlers/
services/
session
{ attrMap... }
By using a syntax like { attrMap... }
it becomes possible to spread any variable of type templ.Attributes
a map[string]any
type definition. Quite useful to define a list of arguments in a single map which will be spread out when generating the component. The way it will be spread out depends on the type of values within the key of the map. Let see what will happen for each types
- if value is a
string
:<div name="value">
- if value is a
bool
:<div name>
if true - if value is a
templ.KeyValue[string, bool]
: `if true- if value
templ.KeyValue[bool, bool]
:<div name>
if both trueURLs
href
require atempl.SafeURL
instead of just a string, you can usetempl.URL
to sanitizes your stringtempl component(p Person) { <a href={ templ.URL(p.URL) }>{ strings.ToUpper(p.Name) }</a> }
Also URLs sanitised used outside of
href
or<form action=""
should be converted back to strings<div hx-get={ string(templ.URL(fmt.Sprintf("/contacts/%s/email", contact.ID)))}>
Composition
Imagine you have a layout that you want to apply to all your pages
templ heading() { <h1>Heading</h1> } templ layout(contents templ.Component) { <div id="heading"> @heading() </div> <div id="contents"> @contents </div> } templ paragraph(contents string) { <p>{ contents }</p> }
You can now inject your content into your
layout
package main import ( "context" "os" ) func main() { c := paragraph("Dynamic contents") layout(c).Render(context.Background(), os.Stdout) }
you can also pass
templ
components as parameters to other componentstempl root() { @layout(paragraph("Dynamic contents")) }
components can be joined together
templ helloWorld() { @templ.Join(hello(), world()) }
If you want to reuse a component outside it’s package, it’s first letter should be a capital letter. package components
Package components templ Hello() { <div>Hello</div> }
Importing Javascript
To import Javacript you just have to add something like that in your head component.
templ head() { <head> <script src="https://unpkg.com/lightweightcharts/dist/lightweight-charts.standalone.production.js"></script> </head> }
You can then use the imported JavaScript in any templ via a
<script>
tag.To serve the file you can assign a new Handler for your static assets and serve it, all of that using the
net/http
standard library.mux := http.NewServeMux() mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets")))) http.ListenAndServe("localhost:8080", mux)
Importing HTMX
HTMX can be used to selectively replace content within a web page. To make it available to your project you just have to download
htmx.min.js
and serve it. You can then add a<script>
tag to the<head>
section of your HTML, like we’ve shown above.<script src="/assets/js/htmx.min.js"></script>
Context
allows to pass parameters down the chain of components using the implicit
ctx
variable// Define the context key type. type contextKey string // Create a context key for the theme. var themeContextKey contextKey = "theme" // Create a context variable that inherits from a parent, and sets the value "test". ctx := context.WithValue(context.Background(), themeContextKey, "test") // Pass the ctx variable to the render function. themeName().Render(ctx, w)
it is then accessible from within components like thtat
templ themeName() { <div>{ ctx.Value(themeContextKey).(string) }</div> }
Standard modules imported by TEMPL
templ
import the following standard modules- fmt
- strings
- html
- html/template
- net/http
- bytes
- context
- crypto/sha256
- embed
- encoding/hex
- encoding/json
- errors
- io
- os
- reflect
- runtime
- sort
- strconv
- sync
- sync/atomic
- time
Dependencies (37)
Go module dependency management will also install the following packages
- x/crypto supplementary Go cryptography packages.
- x/sync supplementary Go concurrency primitives
- x/net supplementary Go networking packages
- x/sys supplementary Go packages for low-level interactions with the operating system
- x/tools various tools and packages mostly for static analysis of Go programs
- x/telemetry Go Telemetry server code and libraries
- x/term Go terminal and console support packages.
- x/text supplementary Go packages for text processing
- x/xerrors transition packages for the new Go 1.13 error values
- yaml- YAML support for the Go language
- asm algorithms that use the full power of modern CPUs to get the best performance.
- goquery a set of features similar to jQuery
- spew implements a deep pretty printer for Go data structures to aid in debugging.
- Color lets you use colorized outputs in terms
- jsonrpc2 provides a Go implementation of JSON-RPC 2.0.
- Zap provides fast, structured, leveled logging
- atomic a go package for atomic file writing
- backoff a Go port of the exponential backoff algorithm
- browser helpers to open URLs, readers, or files in the system default web browser.
- fsnotify cross-platform filesystem notifications on Windows, Linux, macOS, BSD, and illumos.
- mod for direct manipulation of Go modules themselves.
- brotlibrotli compressor and decompressor
- parsea set of parsing tools for Go inspired by Sprache.
- uri an implementation of the URI Uniform Resource Identifier(RFC3986)
- go-isatty test whether a file descriptor refers to a terminal
- go-colorable colorable writer for windows.
- encoding implementations of encoders and decoders for various data formats
- multierr allows combining one or more Go errors together
- cascadia implements CSS selectors for use with the parse trees produced by the html package
- cmp determines equality of values
- kr/text for manipulating paragraphs of text.
- pmezard/go-difflib partial port of python 3 difflib package
- rs/cors a
net/http
handler implementing Cross Origin Resource Sharing W3 specification in Golang - stretchr/objx for dealing with maps, slices, JSON and other data
- stretchr/testify provide many tools for testifying that your code will behave as you intend
- yuin/goldmark - A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
- goleak Goroutine leak detector to help avoid Goroutine leaks.
I know I said simple static page, but remember that your Go code will be statically compiled, so all these dependencies are hidden from your end user. At least we know the building blocks of templ. I always like to break things down to understand the inner workings of things. Iif you ever want to see the above list of dependencies, enter the module and run
go list -m all
I hope this article was useful, I’ll experiment more and may update this article in the future.
Links
- TEMPL docs
- TEMPL Github
- TEMPL apps examples
- if value