In this post I'm going to run through a start-to-finish tutorial for building a online contact form in Go. I'll be trying to explain the different steps in detail, as well as outlining a sensible pattern for processing that can be extended to other forms.
Let's begin the example by creating a new directory for the application, along with a
main.go file for our code and a couple of vanilla HTML templates:
Our contact form will issue a POST request to
/, which will be the same URL path that we use for presenting the form. This means that we'll need to route requests for the same URL to different handlers based on the HTTP method.
There are a few ways of achieving this, but we'll use Pat – a third-party routing library which I've talked about before. You'll need to install it if you're following along:
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 visit localhost:3000 in your browser you should see the contact form, although it doesn't do anything yet.
Now for the interesting part. Let's add a couple of validation rules to our contact form, display the 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.
One approach to setting this up is to add the code 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 new
Message type, consisting of the
Content values (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 summarily 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.
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 object with them, and call
Validate(). If the validation fails we'll then want to reshow the contact form, passing back the relevant
As a side note, in this example above we're using the FormValue method on the request to access the POST data. We could also access the data directly via
r.Form, but there is a gotcha to point out – by default
r.Form will be empty until it is filled by calling ParseForm on the request. Once that's done, we can access it in the same way as any url.Values type. For example:
Anyway, let's update our template so it shows the validation errors (if they exist) above the relevant fields, and repopulate the form inputs with any information that the user previously typed in:
Go ahead and give it a try:
Still, our contact form is pretty useless unless we actually do something with it. Let's add a
Deliver method which sends the contact form message to a particular email address. In the code below I'm using Gmail, 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 redirect to the confirmation page that we made earlier:
If mapping form data to objects is something that you're doing a lot of, you may find Gorilla Schema's automatic decoder useful. If we were using it for our contact form example, the code would look a bit like this:
Additionally, Goforms appears to be a promising idea, with a fairly slick Django-like approach to dealing with forms. However, the existing validation options are fairly limited and the library doesn't seem to be under active development at the moment. It's still worth a look though, especially if you're thinking of rolling something a bit more generic for your form handling.
If you found this post useful, you might like to subscribe to my RSS feed.