Writing type-safe HTML in Go with htmlgo

Posted on March 17, 2019
by Julian Vossen

Recently, I’ve been working on a server-side-rendered web app written in Go. We are using the standard library package html/template for rendering HTML pages. html/template provides a brilliant escaping mechanism, which escapes data appropriately by looking at the context into which the data is rendered.

Here is an example for this contextual escaping taken from the html/template docs, where the column on the left is the template and the column on the right is the result of inserting "O'Reilly: How are <i>you</i>?" into {{.}}:

{{.}}                            O'Reilly: How are &lt;i&gt;you&lt;/i&gt;?
<a title='{{.}}'>                O&#39;Reilly: How are you?
<a href="/{{.}}">                O&#39;Reilly: How are %3ci%3eyou%3c/i%3e?
<a href="?q={{.}}">              O&#39;Reilly%3a%20How%20are%3ci%3e...%3f
<a onx='f("{{.}}")'>             O\x27Reilly: How are \x3ci\x3eyou...?

While this escaping and the simple API of html/template is great, as projects grow, the lack of type-safety can become problematic. Templates are not type-checked during compilation. This means that there is no guarantee that data accessed during the template execution actually exists. If it doesn’t exist, this will lead to runtime errors instead of errors during compilation. Therefore, the template user needs to know what data a template requires. This becomes hard to keep track of, especially, with nested templates. There is no way a template can define what data it requires so that this data could be checked during compilation.

For type-safe templates there are solutions like quicktemplate which use their own templating language and code generation to generate Go code for templates. While this might work great (and fast) in many cases, I was looking for a way to write pages more in the style of single-file components in Vue, rather than separating views and controllers. Also, the special (and at times slightly verbose) syntax introduced by some of the compiled-template libraries can spark various degrees of enthusiasm.

At this time I was toying around with writing a web app in Haskell. I really enjoyed using blaze-html by Jasper Van der Jeugt which lets you write HTML by using Haskell functions to create HTML elements. Of course, one of the merits of blaze-html is its speed, but I simply liked the idea of writing HTML in a compiled language in the same file as the business logic. This made me think of how we could recreate this experience in Go and the idea for github.com/julvo/htmlgo was born.

Let’s first look at an example of creating a form with an email input and a submit button using htmlgo:

This will result in the following HTML:

While most of the syntax may be obvious to you, you might be wondering what these underscore suffixes are for. For understanding this, let’s look at the two basic functions available for creating tags and attributes:

  1. Tagname(attrs []a.Attribute, children ...HTML) HTML
  2. Attributename(data interface{}, templates ...string) a.Attribute

In both cases, the underscore suffix omits the first argument, i.e. resulting in a tag without attributes, or an attribute without the data argument, respectively:

  1. Tagname_(children ...HTML) HTML
  2. Attributename_(templates ...string) a.Attribute

The function for creating attributes deserves more attention, as the arguments data and templates might be confusing. The idea behind using these two arguments is that you can use templating to create attributes by providing a template and the data that should be rendered into the template. Under the hood, this will use html/template and escape the input contextually. E.g. you could use a.Href(ID, "/details/{{.}}") for an anchor element, whereby ID will be escaped securely. Note, as templates is a variadic argument, it can be omitted entirely, in which case {{.}} is used as the template. In many cases, the attribute values may be static. For static values, you could write Type(nil, "email") or the unescaped and less verbose underscore-suffixed equivalent Type_("email"). A conservative rule of thumb is that you should never pass a variable into the underscore-suffixed functions, only string literals.

For converting text to HTML, there is Text(text interface{}) HTML and the unescaped Text_(text string) HTML.

A special case is the script tag Script(attrs []a.Attribute, js JS) HTML where is second argument is of type JS. You can create a JS by using JavaScript(data interface{}, templates ...string) JS and JavaScript_(templates ...string) JS, whereby the escaping behaves as it does for the attributes.

Now knowing the basics, let’s see how we can wrap the form from the above example into a function to create a nicely reusable component, that would give us compile-time errors if we pass it the wrong data type.

If we were to rename the User.Email field to User.EmailAddr, we would get a compilation error, whereas using the html/template version below would result in a runtime error while executing the template:

In this case, the user of the subscribe_form template would need to scan the templates for occurrences of the type to be changed.

Finally, here is a full example of using htmlgo to actually render a page:

As of now, htmlgo is safe to use as it is based on html/template’s contextual escaping. It supports for all HTML5 tags and attributes. However, at this point it is not optimised for performance and will likely be significantly slower than html/template.

Feel free to play around with htmlgo and raise issues on Github.