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:
|GET||/||home||Display the contact form|
|POST||/||send||Submit the contact form|
|GET||/confirmation||confirmation||Display a confirmation message after successful submission|
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 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
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
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
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.io 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.