Documentation › Traditional web application › Working with HTML forms
The generated codebase includes a request.DecodePostForm() function in internal/request/forms.go, which you can use to decode data from HTML forms using method="POST" into a struct. Behind the scenes, decoding is handled using the go-playground/form package.
To illustrate how this works, let's say you have created a page template at assets/templates/pages/create-person.tmpl containing the following HTML form:
{{define "page:title"}}Create a new person{{end}}
{{define "page:main"}}
<h2>Create a new person</h2>
<form action="/person/create" method="POST">
<div>
<label>Your name:</label>
<input type="text" name="Name" value="{{.Form.Name}}">
</div>
<div>
<label>Your age:</label>
<input type="number" name="Age" value="{{.Form.Age}}">
</div>
<button>Submit</button>
</form>
{{end}}
Let's also assume that your application routes are configured so that GET /person/create and POST /person/create both map to a createPerson() handler.
You can then display and process the form like this:
package main
import (
"fmt"
"net/http"
"example.com/internal/request"
"example.com/internal/response"
)
func (app *application) createPerson(w http.ResponseWriter, r *http.Request) {
// Declare a struct to hold the form data.
var form struct {
Name string `form:"Name"`
Age int `form:"Age"`
}
switch r.Method {
case http.MethodGet:
// Add any default data values to the form.
form.Age = 21
data := app.newTemplateData(r)
data["Form"] = form
err := response.Page(w, http.StatusOK, data, "pages/create-person.tmpl")
if err != nil {
app.serverError(w, r, err)
}
case http.MethodPost:
// Decode the HTML form data into the form struct.
err := request.DecodePostForm(w, r, &form)
if err != nil {
app.badRequest(w, r, err)
return
}
fmt.Fprintf(w, "%s is %d", form.Name, form.Age)
}
}
One of the most important things to notice in this example is the use of the form struct tags:
var form struct {
Name string `form:"Name"`
Age int `form:"Age"`
}
These tags tell the decoder how to map values from the HTML form inputs to the struct fields, based on the name attribute of each input.
For example, the value from the <input type="number" name="Age" ...> input will be decoded into the Age field.
The destination fields in the struct must also be exported (that is, begin with a capital letter) for decoding to work.
If there is a field in the struct that you do not want form data decoded into, you can tell the decoder to ignore it using the `form:"-"` struct tag.
If the HTML form data cannot be decoded successfully – for example, if a text value cannot be converted into an int field – then request.DecodePostForm() will return an error, which you can handle using the app.badRequest() helper.
The internal/validator package includes a simple but powerful validator.Validator type that you can use to perform validation checks. A nice pattern is to embed it in your form struct, using the `form:"-"` tag so that no form data is accidentally decoded into it.
Extending the previous example:
package main
import (
"fmt"
"net/http"
"example.com/internal/request"
"example.com/internal/response"
"example.com/internal/validator"
)
func (app *application) createPerson(w http.ResponseWriter, r *http.Request) {
var form struct {
Name string `form:"Name"`
Age int `form:"Age"`
Validator validator.Validator `form:"-"`
}
switch r.Method {
case http.MethodGet:
form.Age = 21
data := app.newTemplateData(r)
data["Form"] = form
err := response.Page(w, http.StatusOK, data, "pages/create-person.tmpl")
if err != nil {
app.serverError(w, r, err)
}
case http.MethodPost:
err := request.DecodePostForm(w, r, &form)
if err != nil {
app.badRequest(w, r, err)
return
}
form.Validator.CheckField(form.Name != "", "Name", "Name is required")
form.Validator.CheckField(form.Age != 0, "Age", "Age is required")
form.Validator.CheckField(form.Age >= 21, "Age", "Age must be 21 or over")
if form.Validator.HasErrors() {
data := app.newTemplateData(r)
data["Form"] = form
err := response.Page(w, http.StatusUnprocessableEntity, data, "pages/create-person.tmpl")
if err != nil {
app.serverError(w, r, err)
}
return
}
fmt.Fprintf(w, "%s is %d", form.Name, form.Age)
}
}
When using this pattern, you can then display validation error messages in your HTML form like this:
<form action="/person/create" method="POST">
{{if .Form.Validator.HasErrors}}
<p>Something was wrong. Please correct the errors below and try again.</p>
{{end}}
<div>
<label>Your name:</label>
{{with .Form.Validator.FieldErrors.Name}}
<span class='error'>{{.}}</span>
{{end}}
<input type="text" name="Name" value="{{.Form.Name}}">
</div>
<div>
<label>Your age:</label>
{{with .Form.Validator.FieldErrors.Age}}
<span class='error'>{{.}}</span>
{{end}}
<input type="number" name="Age" value="{{.Form.Age}}">
</div>
<button>Submit</button>
</form>
The internal/request/forms.go file also includes a request.DecodeQueryString() function that you can use to decode data from HTML forms submitted with method="GET" – or URL query string data more generally – into a struct.
For example, if you have an HTML form like this:
<form action="/filter" method="GET">
<div>
<label>Make:</label>
<input type="text" name="make" placeholder="Make">
</div>
<div>
<label>Model:</label>
<input type="text" name="model" placeholder="Model">
</div>
<button>Filter</button>
</form>
Or, more generally, if you want to process a URL with a query string such as /filter?make=Ford&model=Mustang, you can use the request.DecodeQueryString() function like so:
func (app *application) filter(w http.ResponseWriter, r *http.Request) {
var form struct {
Make string `form:"make"`
Model string `form:"model"`
}
err := request.DecodeQueryString(r, &form)
if err != nil {
app.badRequest(w, r, err)
return
}
fmt.Fprintf(w, "%s %s", form.Make, form.Model)
}
Again, struct tags are used to tell the decoder how to map values from the HTTP request into struct fields. The struct fields must also be exported (start with a capital letter) for decoding to work.