Create beautiful PDFs with Golang and AWS Lambda

Kamil Piotrowski
Nordcloud Engineering
5 min readMar 4, 2019

Golang templates allow you to generate customizable HTML documents based on the schema and input data. They are an advanced formatting language with conditions, loops, pipelines and custom formatting functions. Templates are mostly used for generating HTML documents that can be served by an HTTP server. However, they can be used for many more use cases. In this article, I will show how to generate an HTML document that will be used as an input for PDF-builder Lambda. If you want to know how we have created a Lambda that generates PDF - please follow us. We will release this article soon.

Prepare data structure

Because Go is a strongly types language, before we create a template schema we need to think about what we want to present and prepare a data structure. For our example, I will use the following, simple structure:

type Report struct {
ID string `json: "id"`
Jobs []Job `json: "jobs"
}
Type Job struct {
ID string `json: "id"
Start time.Time `json: "start"
End time.Time `json: "end"
User *string `json: "user"
Status string `json: "status"
MetaInformation map[string]string `json: "meta_information"
}

Report contains a list of the jobs that have been processed by the system. Every job is described by execution status and the date time. Our task is to convert this structure to a PDF document that will be understandable for the end user.

Please note that every field that you want to use in the template has to be exported, this means it needs to start with a capital letter. In other case, template engine will return an error. It’s fine to use pointers for the fields that may be empty. However, accessing nil value will result in an error so you need to make sure that field is safe to display by using if else statement in your template.

Create template

Once the data structure is ready, it’s time to create the template schema. You can either assign it to the variable or create a separate file containing the template. The difference is the function used for parsing the template. In this section, I presented the basic features of the template engine. If you want to know more please visit this site.

Accessing fields

Golang template engine uses moustache{{ }} syntax to represent dynamic actions. You can access the data using the{{ .FieldName }} or the {{ . }} (for the non-struct types) syntax. Please remember that the variable you want to refer to cannot be nil, otherwise, engine return nil pointer dereference error.

Example:

<h1>{{ .ID }}</h1> //Report.ID

Loop over a list and a map

For displaying iterable data types, like lists or maps, you can use the loop syntax in a template. There is a slight difference in how you iterate over list and map items. For the list, you have to use the {{ range .ListFieldName }} … {{ end }} syntax. Inside the loop you are in the iterated item scope, so you can access its data in the standard way.

Example:

{{ range .Jobs }}
<h2>{{ .ID }}</h2> //Report.Jobs[].ID
<h4>{{ .Status }}</h4> //Report.Jobs[].Status
<h4>{{ .StartTime.Format "02.01.2006 15:04" }}</h4>
<h4>{{ .StartTime.Format "02.01.2006 15:04" }}</h4>
{{ end }}

To iterate over the map you have to use the {{ range $key, $value := .MapFieldName }} .. {{ end }} syntax. Inside loop you can access $key and $value variables using the {{ $key }} or {{ $value }} statement.

Example:

{{ range $key, $value := .MetaInformation }}
<h5><b>{{ $key }}</b>{{ $value }}</h5>
{{ end }}

If … else statements

For the conditional actions, you can use the {{ if condition }} .. {{ else }} .. {{ end }} syntax. What’s more, you can combine multiple conditions using the and or the or operator inside the if function: {{ if and condition1 condition2 }}.

Example:

{{ if .User }} 
{{ .User }}
{{ else }}
unknown user
{{ end }}
{{ if not (eq (len .MetaInformation) 0 ) }}

{{else }}
Missing meta information
{{ end }}

The above example protects us from the accessing nil variable (User field is a pointer) and shows how to check if map or list is not empty.

Pipelines and custom formatters

Another interesting feature of golang templates are the pipelines. A pipeline is a sequence of formatting commands and arguments separated with the | character. The real fun begins when you can create your own, custom formatting function. What you need to do is to create a variable of type template.FuncMap containing the functions definitions.

Example:

import (
"html/template"
)
var funcMap = template.FuncMap{
"ToLower": strings.ToLower,
"Remove_": func(val string) string {
return strings.Replace(val, "_", "", -1)
},
}

Example:

<h4>{{ .Status | ToLower | Remove_ }}</h4> //Report.Jobs[].Status

Execute Template

Once the data structure and the template are ready we can combine them and generate the final document:

buf := &bytes.Buffer{}
t := template.New("template.html").Funcs(funcMap)
t, err := t.ParseFiles("/templates/template.html") //path to the template
if err != nil {
log.Error("Failed to parse report template", err.Error())
return err
}
err = t.Execute(buf, data) //Execute template, output in buf
if err != nil {
log.Error("Failed to generate report template", err.Error())
return err
}

The above example shows how to load the template from the file. Please note that the name of the template has to be the same as one of the parsing files names. Otherwise, template parser will fail. If you defined your own formatting functions you can bind them with the template using the .Funcs() method.

Summary

Golang templates deliver really powerful formatting language that can be used for generating various documents. Advanced actions like loops, conditions, and pipelines allows you to focus more on how the document should look like rather than how to build it. If you want you can even use your own, custom formatting functions. That feature makes golang templates really customizable.

In Nordcloud we are using templates to generate reports from our applications, that are converted to PDF’s, and delivered to the end users. If you are interested in how we built our pdf-builder lambda please follow us. We will release this article soon.

At Nordcloud we are always looking for talented people. If you enjoy reading this post and would like to work with GO projects on a daily basis — check out our open positions here.

--

--