Form Validation and Processing in Go
In this post I want to outline a sensible pattern that you can use for validating and processing HTML forms in Go web applications. Over the years I've tried out a number of different approaches, but this is the basic pattern that I always keep coming back to. It's clear and uncomplicated, but also flexible and extensible enough to work well in a wide variety of projects and scenarios.
To illustrate the pattern, I'll run through the start-to-finish build of a simple online contact form.
So let's begin by creating a new directory for the application, along with a main.go
file for our code
and a couple of vanilla HTML templates:
If you're following along you'll also need to enable modules
in the application root by running the go mod init
command like so:
Once that's done, your directory structure should look like this:
Displaying the Form
Our application is going to provide three routes:
Method | URL Path | Handler | Description |
---|---|---|---|
GET | / | home | Display the contact form |
POST | / | send | Submit the contact form |
GET | /confirmation | confirmation | Display a confirmation message after successful submission |
To handle the routing of requests we're going to use bmizerany/pat
– but if you want to use an
alternative Go router please feel free.
Let's go ahead and create a skeleton for the application:
This is fairly straightforward stuff so far. The only real point of note is that we've put the template handling into
a render
function to cut down on boilerplate code.
If you run the application:
And then visit http://localhost:3000
in your browser you should see
the contact form being displayed (although it doesn't do anything yet!).
Validating the Form
Now for the interesting part. Let's add some validation rules to this contact form, display the validation errors if there are any, and make sure that the form values get presented back if there's an error so the user doesn't need to retype them.
We could add the code for this inline in our send
handler, but personally I find it cleaner and
neater to break out the logic into a separate message.go
file:
So what's going on here?
We've started by defining a rxEmail
variable, containing a simple regular expression for validating the
format of the email address in the form.
Then we define a Message
struct, consisting of Email
and Content
fields (which
will hold the data from the submitted form), along with an Errors
map to hold any validation error
messages.
We then created a Validate()
method that acts on a given Message
, which checks the format
of the email address and makes sure that the content isn't blank. In the event of any errors we add them to the
Errors
map, and finally return a true
or false
value to indicate whether
validation passed successful or not. In a large project you might want to break the validation checks into helper
functions to reduce duplication.
This approach means that we can keep the code in our send
handler fantastically light. All we need it to
do is retrieve the form values from the POST request, create a new Message
instance containing them, and
call Validate()
. If the validation fails we can re-render the contact form, passing back the relevant
Message
struct. Like so:
As a side note, in the code above we're using the PostFormValue()
method on the request
to access the POST data. This is a helper method which parses the form data in the request body (using ParseForm()
) and returns the value for a
specific field. If no matching field exists in the request body, it will return the empty string ""
.
For large request bodies, you might also want to consider using the gorilla/schema
package to automatically decode
the form values in to a struct, instead of assigning them manually like we have done in the code above.
Anyway, let's now update our home.html
template so it displays the validation errors (if they exist)
above the relevant fields, and repopulate the form inputs with any information that the user previously typed in:
Let's try this out. Go ahead and run the application:
And try submitting an invalid form. You should find that the form is redisplayed along with the relevant data and validation errors like so:
Sending the Contact Form Message
Great! That's now working nicely, but our contact form isn't very useful unless we actually do something with it.
Let's add a Deliver()
method to our Message
which sends the contact form message to a
particular email address. In the code below I'm using the go-mail/mail
package and a Mailtrap account for email sending, but the same thing should work with any other SMTP
server.
The final step is to head back to our main.go
file, add some code to call Deliver()
, and
issue a 303 See Other
redirect to the confirmation page that we made earlier:
So long as your SMTP server account credentials are set up correctly, you should now be able to successfully submit the contact form and you should see the confirmation message below in your browser.