Using Hugo with a database (finally)

I know this has been asked and answered a few times, but I’ve figured out a way of doing just this.

The key here is using hugo to generate the template for a (golang) web application.

this is using hugo with a database; not using a database with hugo. Hugo is just used for generating the template that the web application uses.

Put all the escaped template logic into the config.toml and run it through safeHTML in the template


use arrays in config.toml and range their contents. A brief example:


    oneProductPage = [          "{{$product := findProduct1 .Table .PartNumber}}",
                                              "{{range $product}}",]
    end = [                               "{{end}}"]

then, in the template

        {{range .Site.Params.oneProductPage}}{{ . | safeHTML }}{{end}}
        {{range .Site.Params.end}}{{ . | safeHTML }}{{end}}

This gets quite complex but it works. There are little tricky things like how menus don’t need to have escapes in the code. Everything else should be escaped. If you don’t use an array, you can’t divide up html elements and everything has to be on the same line.

Another thing I’ve done is to put the funcs for a go web app in the template file for a certain page type


title = "{{$product := findProduct1 .Table .PartNumber}}{{range $product}}{{.Name}}{{end}}"
description = "{{$product := findProduct1 .Table .PartNumber}}{{.Description1}}{{end}}"
date = "2021-04-02"
author = ""
layout = "product"

here are some parts of the web app this works with

I recommend using statikFS as shown here for all the resources of the site to be available

package route
import (

  _ ""


func Server(webPort int){
  statikFS, err := fs.New()
  if err != nil {
  r := mux.NewRouter() //.StrictSlash(true)
  r.NotFoundHandler = handle.Page404()
  r.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("./img")))) //images
  r.Handle("/post/{slug}", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(handle.FindProduct))).Methods("GET")	//individual product page
  r.Handle("/{slug}", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(handle.FrontPage))).Methods("GET")
  r.Handle("/", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(handle.FrontPage))).Methods("GET") //site root
  r.Handle("/{slug}/{id:[0-9]+}", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(handle.FrontPage))).Methods("GET") // /p/ for pagination
  r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(statikFS)))	//statik sources
  r.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // this handler behaved as I would expect the r.NotFoundHandler to behave..
    w.Write([]byte(`{"status":501,"message":"501: Not implemented."}`))
  Serve := r
  fmt.Printf("listening on using gorilla router\n", webPort)
  log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", webPort), Serve))

here’s an http handler - note that it’s using a file generated by hugo: /public/post/product/index.html

// // individual product page ENDPOINT: /post/{slug} // //
func FindProduct(w http.ResponseWriter, r *http.Request) {	//, product string
  var title01 string
	slug := mux.Vars(r)["slug"]
	a := false
	for i := range inv.Mproducts {
		if inv.Mproducts[i].PartNo == slug {
      title01 = inv.Mproducts[i].Name
      if inv.Mproducts[i].Category == "resistor" {
        title01 = title01 + " resistor"
			a = true
			break				// Found!
if !a {
	fmt.Fprint(w, "No product found for part number:\n", slug)
} else {
	productp := funcmap.Page{title01, slug, "products", "", "0", 0}
	wd, err := os.Getwd()
	if err != nil { log.Fatal(err)	}
	tpl1 := template.Must(template.New("").Funcs(funcmap.FM).ParseFiles(wd + "/public/post/product/index.html"))
	if err :=	tpl1.ExecuteTemplate(w, "index.html", productp); err != nil {	fmt.Printf("error: %s", err) }

here’s a template function

	// // returns the database entry given the part number // //
	func findProduct1(table string, partno string) []inv.Product {	//, product string
		var ppartno []inv.Product
		if table == "products" {
		for i := range inv.Mproducts {
			if inv.Mproducts[i].PartNo == partno {
				ppartno = append(ppartno, inv.Mproducts[i])
				break				// Found!
		return ppartno

For my site I’m using cockroachdb, and using upper/db as the database access layer.

The most connfusing thing is just switching back and forth in your mind (in the template) between what hugo is going to interpret and what is supposed to be passed to the final template that hugo is generating.

I’ve never seen anyone do this before. So I’m not sure how strange this may seem. Hugo is a fantastic tool, that’s all I can say,