Setting Up Templ Using Nix Flakes
Templ is an HTML templating package that compiles to Go code,
and its approach should be familiar to anyone who has used React or a similar component-driven
JavaScript framework. One requirement of using this library is that your templates are written
as .templ files, and generate Go code for runtime, which should also be familiar to JavaScript
developers ;-).
Since I’ve started kicking the tires on Templ with Nix flakes, I figured this would make for a great tutorial topic.
Setting Up The Flake
Let’s start with a basic flake that can run a Go application.
{
description = "A simple Go application";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, utils, ...}:
utils.lib.eachDefaultSystem(system:
let pkgs = import nixpkgs { inherit system };
in {
devShell = with pkgs; mkShell {
buildInputs = [
go
gopls
gotools
go-tools
];
};
})
}
If you’re new to Nix, I’ll walk through what’s going on here:
- 2
inputsare defined,nixpkgs, which points to the unstable branch andutils, which points to a utility library for working with flakes. - The
outputsattribute then takesself, and the 2 inputs we declared above,nixpkgsandutils. utilshas a helper function that will iterate over each default system (x86_64-linux, darwin, etc) and passessystemas an argument to the handler.- A
pkgsbinding is created which importsnixpkgsand inherits the passed system’s attributes (you can think of this like an object spread operator in JavaScript,{...system}). - The resulting object returned has a
devShellattribute where the packages needed for this project are declared on thebuildInputs. In this case it’s Go, its ALanguage Server and some tooling.
Installing Templ
And as luck would have it, Templ provides an official flake. Let’s install by adding
it to the inputs attribute.
inputs = {
# ... other inputs
templ.url = "github:a-h/templ";
};
Pass the input attribute to outputs.
outputs = { self, nixpkgs, utils, templ }:
Create an function which takes system as an argument and returns the templ
binary, and bind it to the the return value.
let
pkgs = import nixpkgs { inherit system; };
# Function that takes a system and returns a path to the templ package
templOverlay = system: templ.packages.${system}.templ;
Now let’s add the Templ package to the buildInputs so it can be used in the Go application.
devShell = with pkgs; mkShell {
buildInputs = [
# Run the overlay function which passes in the current system below the packages
(templOverlay system)
];
};
As far as development is concerned, this is all that is needed. To get started this is how you would get the flake up and running in your terminal. Note how the first command tells us that we currently don’t have Go installed on our system.
$ go version
The program 'go' is not in your PATH. It is provided by several packages.
You can make it available in an ephemeral shell by typing one of the following:
nix-shell -p gccgo
nix-shell -p gccgo12
nix-shell -p gccgo13
$ nix develop
installing...when done enter a nix shell
> go version
go version go1.22.2 linux/amd64
> templ version
v0.2.668
> templ generate
(✓) Complete [ updates=1 duration=2.093097ms ]
> go run .
Listening on port :3000
# Ctrl-C
> exit
$ go version
The program 'go' is not in your PATH....
Defining a package
Now that we have the ability to develop in an isolated Go environment, let’s set up
the build step. What’s nice about how Nix flakes handles this is that to compile the
application I don’t need to install all the dependencies. Running nix build will
download everything needed to compile my project without needing to pollute my dev machine.
Let’s start by adding a packages attribute on the flake. It’ll contain:
- The name of the package name,
pname, which matches the attribute name. - The version number.
- The src directory, which is useful if you have your files in a subdirectory.
- The vendor hash for verification.
- A
preBuildaction which compiles all the.templfiles to.gobefore running the main build task. - Lastly create a
defaultPackageattribute that points to thepackages.templ-appattribute.
packages = {
templ-app = pkgs.buildGoModule {
pname = "templ-app";
version = "0.1.0";
src = ./.;
vendorHash = pkgs.lib.fakeHash;
};
# Prebuild all templates before compiling
preBuild = ''
${templOverlay system}/bin/templ generate
'';
};
defaultPackage = self.packages.${system}.templ-app;
Compiling An Executable
The first time nix build is ran, use vendorHash = pkgs.lib.fakeHash;.
Here I followed the notes outlined in the official Nix flakes
Go example project :
This hash locks the dependencies of this package. It is necessary because of how Go requires network access to resolve VCS. See https://www.tweag.io/blog/2021-03-04-gomod2nix/ for details. Normally one can build with a fake hash and rely on native Go mechanisms to tell you what the hash should be or determine what it should be “out-of-band” with other tooling (eg. gomod2nix). To begin with it is recommended to set this, but one must remember to bump this hash when your dependencies change.
Essentially this states that the first nix build run will return an error with
an expected and recieved vendor hashs. To resolve this, simply copy the expected hash
and replace the fakeHash variable. It also notes that this should be updated every
time your dependencies change. Or at least that’s how best I understand it from my
research for this tutorial, I’ll be sure to update if I find more clarity on how it works.
So to wrap it all up, let’s build the finished executable.
$ nix build
$ ./result/bin/nix-templ
Listening on :3000
Next Steps
From here the executable can be installed globally as $ nix-templ or
distributed as a package. Hopefully this tutorial gets you started with Go and
its great ecosystem of packages running in a Nix flake development environment.