nix-go-templ.txt

Tutorial

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 inputs are defined, nixpkgs, which points to the unstable branch and utils, which points to a utility library for working with flakes.
  • The outputs attribute then takes self, and the 2 inputs we declared above, nixpkgs and utils.
  • utils has a helper function that will iterate over each default system (x86_64-linux, darwin, etc) and passes system as an argument to the handler.
  • A pkgs binding is created which imports nixpkgs and 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 devShell attribute where the packages needed for this project are declared on the buildInputs. 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 preBuild action which compiles all the .templ files to .go before running the main build task.
  • Lastly create a defaultPackage attribute that points to the packages.templ-app attribute.
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.

977 Words

Published