Go form handling

Posted on Aug 16, 2020

Instead of passing a name argument as a URL parameter as we did in our Hello World example, we can improve the user experience using HTML forms. The built-in http.Request object gives us access to posted form variables, using the PostFormValue() method.

We’ve removed the name argument from the URL pattern, and have the GET handler return the template without any context. In this case, the template will present the user with a <form>.

An additional POST handler accesses the name form value and attaches an error to the template context in case it’s missing.

package main

import (
    "errors"
    "html/template"
    "net/http"
    "strings"

    "github.com/go-chi/chi"
)

var funcMap = template.FuncMap{
    "Capitalize": strings.Title,
}

var tpl = template.Must(template.New("page").Funcs(funcMap).ParseFiles(
    "templates/index.gohtml", "templates/greeting.gohtml"))

type tplData struct {
    Greeting, Name string
    Errors         []error
}

func NewGreetingsRouter() *chi.Mux {
    router := chi.NewRouter()

    router.Get("/{greeting}", func(w http.ResponseWriter, r *http.Request) {
        tpl.ExecuteTemplate(w, "index", nil)
    })

    router.Post("/{greeting}", func(w http.ResponseWriter, r *http.Request) {
        var formErrors []error

        name := r.PostFormValue("name")
        if name == "" {
            formErrors = append(formErrors, errors.New("missing required 'name' field"))
        }

        data := tplData{
            Greeting: chi.URLParam(r, "greeting"),
            Name:     name,
            Errors:   formErrors,
        }

        tpl.ExecuteTemplate(w, "index", data)
    })

    return router
}

func main() {
    router := chi.NewRouter()
    router.Mount("/greeting", NewGreetingsRouter())

    http.ListenAndServe("localhost:3000", router)
}

The logic for displaying the initial form, possible errors, or the final greeting, has been delegated to the template.

{{ define "greeting" }}
    {{ if .Name }}
        {{ if eq .Greeting "hello" }}
            <p>Hello <strong>{{ .Name | Capitalize }}</strong>!</p>
        {{ else }}
            <p>Goodbye <strong>{{ .Name | Capitalize }}</strong>!</p>
        {{ end }}
        <a href="">try again</a>
    {{ else }}
        <p>Hey stranger, tell me your name:</p>
        {{ if .Errors }}
        <ul>
            {{ range $err := .Errors }}
            <li>{{ $err }}</li>
            {{ end }}
        </ul>
        {{ end }}
        <form method="post">
            <label for="name">Male</label>
            <input type="text" name="name" id="name" />
            <input type="submit" value="submit" />
        </form>
    {{ end }}
{{ end }}
comments powered by Disqus