© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
A. FreemanPro Gohttps://doi.org/10.1007/978-1-4842-7355-5_1

1. Your First Go Application

Adam Freeman1  
(1)
London, UK
 

The best way to get started with Go is to jump right in. In this chapter, I explain how to prepare the Go development environment and create and run a simple web application. The purpose of this chapter is to get a sense of what writing Go is like, so don’t worry if you don’t understand all the language features that are used. Everything you need to know is explained in detail in later chapters.

Setting the Scene

Imagine that a friend has decided to host a New Year’s Eve party and that she has asked me to create a web app that allows her invitees to electronically RSVP. She has asked for these key features:
  • A home page that shows information about the party

  • A form that can be used to RSVP, which will display a thank-you page

  • Validation to ensure the RSVP form is filled out

  • A summary page that shows who is coming to the party

In this chapter, I create a Go project and use it to create a simple application that contains all these features.

Tip

You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/apress/pro-go. See Chapter 2 for how to get help if you have problems running the examples.

Installing the Development Tools

The first step is to install the Go development tools. Go to https://golang.org/dl and download the installation file for your operating system. Installers are available for Windows, Linux, and macOS. Follow the installation instructions, which can be found at https://golang.org/doc/install, for your platform. When you have completed the installation, open a command prompt and run the command shown in Listing 1-1, which will confirm that the Go tools have been installed by printing out the package version.

UPDATES TO THIS BOOK

Go is actively developed, and there is a steady stream of new releases, which means there may be a later version available by the time you read this book. Go has an excellent policy for maintaining compatibility, so you should have no issues following the examples in this book, even with a later release. If you do have problems, see the GitHub repository for this book, https://github.com/apress/pro-go, where I will post free updates that address breaking changes.

This kind of update is an ongoing experiment for me (and for Apress), and it continues to evolve—not least because I don’t know what the future releases of Go will contain. The goal is to extend the life of this book by supplementing the examples it contains.

I am not making any promises about what the updates will be like, what form they will take, or how long I will produce them before folding them into a new edition of this book. Please keep an open mind and check the repository for this book when new versions are released. If you have ideas about how the updates could be improved, then email me at [email protected] and let me know.
go version
Listing 1-1

Checking the Go Installation

The current version at the time of writing is 1.17.1, which produces the following output on my Windows machine:
go version go1.17.1 windows/amd64

It doesn’t matter if you see a different version number or different operating system details—what’s important is that the go command works and produces output.

Installing Git

Some Go commands rely on the Git version control system. Go to https://git-scm.com and follow the installation instructions for your operating system.

Selecting a Code Editor

The only other step is to select a code editor. Go source code files are plain text, which means you can use just about any editor. Some editors, however, provide specific support for Go. The most popular choice is Visual Studio Code, which is free to use and has support for the latest Go language features. Visual Studio Code is the editor I recommend if you don’t already have a preference. Visual Studio Code can be downloaded from http://code.visualstudio.com, and there are installers for all popular operating systems. You will be prompted to install the Visual Studio Code extensions for Go when you start work on the project in the next section.

If you don’t like Visual Studio Code, then you can find a list of available options at https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins. No specific code editor is required to follow the examples in this book, and all the tasks required to create and compile projects are performed at the command line.

Creating the Project

Open a command prompt, navigate to a convenient location, and create a folder named partyinvites. Navigate to the partyinvites folder and run the command shown in Listing 1-2 to start a new Go project.
go mod init partyinvites
Listing 1-2

Starting a Go Project

The go command is used for almost every development task, as I explain in Chapter 3. This command creates a file named go.mod, which is used to keep track of the packages a project depends on and can also be used to publish the project, if required.

Go code files have a .go extension. Use your chosen editor to create a file named main.go in the partyinvites folder with the contents shown in Listing 1-3. If you are using Visual Studio Code and this is your first time editing a Go file, then you will be prompted to install the extensions that support the Go language.
package main
import "fmt"
func main() {
    fmt.Println("TODO: add some features")
}
Listing 1-3

The Contents of the main.go File in the partyinvites Folder

The syntax of Go will be familiar if you have used any C or C-like language, such as C# or Java. I describe the Go language in depth in this book, but you can discern a lot just by looking at the keywords and structure of the code in Listing 1-3.

Features are grouped into packages, which is why there is a package statement in Listing 1-3. Dependencies on packages are made using an import statement, which allows the features they use to be accessed in a code file. Statements are grouped in functions, which are defined with the func keyword. There is one function in Listing 1-3, which is named main. This is the entry point for the application, meaning that this is the point at which execution will begin when the application is compiled and run.

The main function contains a single code statement, which invokes a function named Println, which is provided by a package named fmt. The fmt package is part of the extensive standard library that Go provides, described in Part 2 of this book. The Println function prints out a string of characters.

Even though the details may not be familiar, the purpose of the code in Listing 1-3 is easy to figure out: when the application is executed, it will write out a simple message. Run the command shown in Listing 1-4 in the partyinvites folder to compile and execute the project. (Notice that there is a period after the word run in this command.)
go run .
Listing 1-4

Compiling and Executing the Project

The go run command is useful during development because it performs the compilation and execution tasks in one step. The application produces the following output:
TODO: add some features
If you received a compiler error, then the likely cause is that you didn’t enter the code exactly as shown in Listing 1-3. Go insists on code being defined in a specific way. You may prefer opening braces to appear on their own line, and you may have formatted the code that way automatically, as shown in Listing 1-5.
package main
import "fmt"
func main()
{
    fmt.Println("TODO: add some features")
}
Listing 1-5

Putting a Brace on a New Line in the main.go File in the partyinvites Folder

Run the command shown in Listing 1-4 to compile the project, and you will receive the following errors:
# partyinvites
.main.go:5:6: missing function body
.main.go:6:1: syntax error: unexpected semicolon or newline before {

Go insists on a specific code style and deals with common code elements, such as semicolons, in unusual ways. The details of the Go syntax are described in later chapters, but, for now, it is important to follow the examples exactly as shown to avoid errors.

Defining a Data Type and a Collection

The next step is to create a custom data type that will represent the RSVP responses, as shown in Listing 1-6.
package main
import "fmt"
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
func main() {
    fmt.Println("TODO: add some features");
}
Listing 1-6

Defining a Data Type in the main.go File in the partyinvites Folder

Go allows custom types to be defined and given a name using the type keyword. Listing 1-6 creates a struct data type named Rsvp. Structs allow a set of related values to be grouped together. The Rsvp struct defines four fields, each of which has a name and a data type. The data types used by the Rsvp fields are string and bool, which are the built-in types for representing a string of characters and Boolean values. (The Go built-in types are described in Chapter 4.)

Next, I need to collect Rsvp values together. In later chapters, I explain how to use a database in a Go application, but for this chapter, it will be enough to store the responses in memory, which means that responses will be lost when the application is stopped.

Go has built-in support for fixed-length arrays, variable-length arrays (known as slices), and maps that contain key-value pairs. Listing 1-7 creates a slice, which is a good choice when the number of values that will be stored isn’t known in advance.
package main
import "fmt"
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
func main() {
    fmt.Println("TODO: add some features");
}
Listing 1-7

Defining a Slice in the main.go File in the partyinvites Folder

This new statement relies on several Go features, which are most readily understood by starting at the end of the statement and working backwards.

Go provides built-in functions for performing common operations on arrays, slices, and maps. One of those functions is make, which is used in Listing 1-7 to initialize a new slice. The last two arguments to the make function are the initial size and the initial capacity.
...
var responses = make([]*Rsvp, 0, 10)
...

I specified zero for the size argument create an empty slice. Slices are resized automatically as new items are added, and the initial capacity determines how many items can be added before the slice has to be resized. In this case, ten items can be added to the slice before it has to be resized.

The first argument to the make method specifies the data type the slice will be used to store:
...
var responses = make([]*Rsvp, 0, 10)
...

The square brackets, [], denote a slice. The asterisk, *, denotes a pointer. The Rsvp part of the type denotes the struct type defined in Listing 1-6. Put together, []*Rsvp denotes a slice of pointers to instances of the Rsvp struct.

You may have flinched at the term pointer if you have arrived at Go from C# or Java, which do not allow pointers to be used directly. But you can relax because Go doesn’t allow the types of operations on pointers that can get a developer into trouble. As I explain in Chapter 4, the use of pointers in Go determines only whether a value is copied when it is used. By specifying that my slice will contain pointers, I am telling Go not to create copies of my Rsvp values when I add them to the slice.

The rest of the statement assigns the initialized slice to a variable so that I can use it elsewhere in the code:
...
var responses = make([]*Rsvp, 0, 10)
...

The var keyword indicates that I am defining a new variable, which is given the name responses. The equal sign, =, is the Go assignment operator and sets the value of the responses variable to the newly created slice. I don’t have to specify the type of the responses variable because the Go compiler will infer it from the value that is assigned to it.

Creating HTML Templates

Go comes with a comprehensive standard library, which includes support for HTML templates. Add a file named layout.html to the partyinvites folder with the content shown in Listing 1-8.
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Let's Party!</title>
    <link href=
       "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.1.1/css/bootstrap.min.css"
            rel="stylesheet">
</head>
<body class="p-2">
    {{ block "body" . }} Content Goes Here {{ end }}
</body>
</html>
Listing 1-8

The Contents of the layout.html File in the partyinvites Folder

This template will be a layout that contains the content common to all of the responses that the application will produce. It defines a basic HTML document, including a link element that specifies a stylesheet from the Bootstrap CSS framework, which will be loaded from a content distribution network (CDN). I demonstrate how to serve this file from a folder in Chapter 24, but I have used the CDN for simplicity in this chapter. The example application will still work offline, but you will see HTML elements without the styles shown in the figures.

The double curly braces in Listing 1-8, {{ and }}, are used to insert dynamic content into the output produced by the template. The block expression used here defines placeholder content that will be replaced by another template at runtime.

To create the content that will greet the user, add a file named welcome.html to the partyinvites folder, with the content shown in Listing 1-9.
{{ define "body"}}
    <div class="text-center">
        <h3> We're going to have an exciting party!</h3>
        <h4>And YOU are invited!</h4>
        <a class="btn btn-primary" href="/form">
            RSVP Now
        </a>
    </div>
{{ end }}
Listing 1-9

The Contents of the welcome.html File in the partyinvites Folder

To create the template that will allow the user to give their response to the RSVP, add a file named form.html to the partyinvites folder with the content shown in Listing 1-10.
{{ define "body"}}
<div class="h5 bg-primary text-white text-center m-2 p-2">RSVP</div>
{{ if gt (len .Errors) 0}}
    <ul class="text-danger mt-3">
        {{ range .Errors }}
            <li>{{ .  }}</li>
        {{ end }}
    </ul>
{{ end }}
<form method="POST" class="m-2">
    <div class="form-group my-1">
        <label>Your name:</label>
        <input name="name" class="form-control" value="{{.Name}}" />
    </div>
    <div class="form-group my-1">
        <label>Your email:</label>
        <input name="email" class="form-control" value="{{.Email}}" />
    </div>
    <div class="form-group my-1">
        <label>Your phone number:</label>
        <input name="phone" class="form-control" value="{{.Phone}}" />
    </div>
    <div class="form-group my-1">
        <label>Will you attend?</label>
        <select name="willattend" class="form-select">
            <option value="true" {{if .WillAttend}}selected{{end}}>
                Yes, I'll be there
            </option>
            <option value="false" {{if not .WillAttend}}selected{{end}}>
                No, I can't come
            </option>
        </select>
    </div>
    <button class="btn btn-primary mt-3" type="submit">
        Submit RSVP
    </button>
</form>
{{ end }}
Listing 1-10

The Contents of the form.html File in the partyinvites Folder

To create the template that will be presented to attendees, add a file named thanks.html to the partyinvites folder with the content shown in Listing 1-11.
{{ define "body"}}
<div class="text-center">
    <h1>Thank you, {{ . }}!</h1>
    <div> It's great that you're coming. The drinks are already in the fridge!</div>
    <div>Click <a href="/list">here</a> to see who else is coming.</div>
</div>
{{ end }}
Listing 1-11

The Contents of the thanks.html File in the partyinvites Folder

To create the template that will be presented when an invitation is declined, add a file named sorry.html to the partyinvites folder with the content shown in Listing 1-12.
{{ define "body"}}
<div class="text-center">
    <h1>It won't be the same without you, {{ . }}!</h1>
    <div>Sorry to hear that you can't make it, but thanks for letting us know.</div>
    <div>
        Click <a href="/list">here</a> to see who is coming,
        just in case you change your mind.
    </div>
</div>
{{ end }}
Listing 1-12

The Contents of the sorry.html File in the partyinvites Folder

To create the template that will show the list of attendees, add a file named list.html to the partyinvites folder with the content shown in Listing 1-13.
{{ define "body"}}
<div class="text-center p-2">
    <h2>Here is the list of people attending the party</h2>
    <table class="table table-bordered table-striped table-sm">
        <thead>
            <tr><th>Name</th><th>Email</th><th>Phone</th></tr>
        </thead>
        <tbody>
            {{ range . }}
                {{ if .WillAttend }}
                    <tr>
                        <td>{{ .Name }}</td>
                        <td>{{ .Email }}</td>
                        <td>{{ .Phone }}</td>
                    </tr>
                {{ end }}
            {{ end }}
        </tbody>
    </table>
</div>
{{ end }}
Listing 1-13

The Contents of the list.html File in the partyinvites Folder

Loading the Templates

The next step is to load the templates so they can be used to produce content, as shown in Listing 1-14. I am going to write the code to do this in stages, explaining what each change does as I go. (You may see error highlighting in your code editor, but this will be resolved as I add new code statements to later listings.)
package main
import (
    "fmt"
    "html/template"
)
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
var templates = make(map[string]*template.Template, 3)
func loadTemplates() {
    // TODO - load templates here
}
func main() {
    loadTemplates()
}
Listing 1-14

Loading the Templates in the main.go File in the partyinvites Folder

The first change is to the import statement and declares a dependency on the features provided by the html/template package, which is part of the Go standard library. This package provides support for loading and rendering HTML templates and is described in detail in Chapter 23.

The next new statement creates a variable named templates. The type of value assigned to this variable looks more complex than it is:
...
var templates = make(map[string]*template.Template, 3)
...

The map keyword denotes a map, whose key type is specified in square brackets, followed by the value type. The key type for this map is string, and the value type is *template.Template, which means a pointer to the Template struct defined in the template package. When you import a package, the features it provides are accessed using the last part of the package name. In this case, the features provided by the html/template package are accessed using template, and one of those features is a struct whose name is Template. The asterisk indicates a pointer, which means that the map using string keys that are used to store pointers to instances of the Template struct defined by the html/template package.

Next, I created a new function named loadTemplates, which doesn’t do anything yet but which will be responsible for loading the HTML files defined in earlier listings and processing them to create the *template.Template values that will be stored in the map. This function is invoked inside the main function. You can define and initialize variables directly in code files, but the most useful language features can be done only inside functions.

Now I need to implement the loadTemplates function. Each template is loaded with the layout, as shown in Listing 1-15, which means that I don’t have to repeat the basic HTML document structure in each file.
package main
import (
    "fmt"
    "html/template"
)
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
var templates = make(map[string]*template.Template, 3)
func loadTemplates() {
    templateNames := [5]string { "welcome", "form", "thanks", "sorry", "list" }
    for index, name := range templateNames {
        t, err := template.ParseFiles("layout.html", name + ".html")
        if (err == nil) {
            templates[name] = t
            fmt.Println("Loaded template", index, name)
        } else {
            panic(err)
        }
    }
}
func main() {
    loadTemplates()
}
Listing 1-15

Loading the Templates in the main.go File in the partyinvites Folder

The first statement in the loadTemplates folder defines variables using Go’s concise syntax, which can be used only within functions. This syntax specifies the name, followed by a colon (:), the assignment operator (=), and then a value:
...
templateNames := [5]string { "welcome", "form", "thanks", "sorry", "list" }
...

This statement creates a variable named templateNames, and its value is an array of five string values, which are expressed using literal values. These names correspond to the names of the files defined earlier. Arrays in Go are a fixed length, and the array assigned to the templateNames variable can only ever hold five values.

These five values are enumerated in a for loop using the range keyword, like this:
...
for index, name := range templateNames {
...
The range keyword is used with the for keyword to enumerate arrays, slices, and maps. The statements inside the for loop are executed once for each value in the data source, which is the array in this case, and those statements are given two values to work with:
...
for index, name := range templateNames {
...

The index variable is assigned the position of the value in the array that is currently being enumerated. The name variable is assigned the value at the current position. The type of the first variable is always int, which is a built-in Go data type for representing integers. The type of the other variable corresponds to the values stored by the data source. The array being enumerated in this loop contains string values, which means that the name variable will be assigned the string at the position in the array indicated by the index value.

The first statement within the for loop loads a template:
...
t, err := template.ParseFiles("layout.html", name + ".html")
...
The html/templates package provides a function named ParseFiles that is used to load and process HTML files. One of the most useful—and unusual—features of Go is that functions can return multiple result values. The ParseFiles function returns two results, a pointer to a template.Template value and an error, which is the built-in data type for representing errors in Go. The concise syntax for creating variables is used to assign these two results to variables, like this:
...
t, err := template.ParseFiles("layout.html", name + ".html")
...
I don’t need to specify the types of the variables to which the results are assigned because they are already known to the Go compiler. The template is assigned to a variable named t, and the error is assigned to a variable named err. This is a common pattern in Go, and it allows me to determine if the template has been loaded by checking whether the value of err is nil, which is the Go null value:
...
t, err := template.ParseFiles("layout.html", name + ".html")
if (err == nil) {
    templates[name] = t
    fmt.Println("Loaded template", index, name)
} else {
    panic(err)
}
...

If err is nil, then I add a key-value pair to the map, using the value of name as the key and the *template.Tempate assigned to t as the value. Go uses a standard index notation to assign values to arrays, slices, and maps.

If the value of err isn’t nil, then something has gone wrong. Go provides a function named panic that can be called when an unrecoverable error happens. The effect of calling panic can vary, as I explain in Chapter 15, but for this application, it will have the effect of writing out a stack trace and terminating execution.

Compile and execute the project using the go run . command; you will see the following output as the templates are loaded:
Loaded template 0 welcome
Loaded template 1 form
Loaded template 2 thanks
Loaded template 3 sorry
Loaded template 4 list

Creating the HTTP Handlers and Server

The Go standard library includes built-in support for creating HTTP servers and handling HTTP requests. First, I need to define functions that will be invoked when the user requests the default URL path for the application, which will be /, and when they are presented with a list of attendees, which will be requested with the URL path /list, as shown in Listing 1-16.
package main
import (
    "fmt"
    "html/template"
    "net/http"
)
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
var templates = make(map[string]*template.Template, 3)
func loadTemplates() {
    templateNames := [5]string { "welcome", "form", "thanks", "sorry", "list" }
    for index, name := range templateNames {
        t, err := template.ParseFiles("layout.html", name + ".html")
        if (err == nil) {
            templates[name] = t
            fmt.Println("Loaded template", index, name)
        } else {
            panic(err)
        }
    }
}
func welcomeHandler(writer http.ResponseWriter, request *http.Request) {
    templates["welcome"].Execute(writer, nil)
}
func listHandler(writer http.ResponseWriter, request *http.Request) {
    templates["list"].Execute(writer, responses)
}
func main() {
    loadTemplates()
    http.HandleFunc("/", welcomeHandler)
    http.HandleFunc("/list", listHandler)
}
Listing 1-16

Defining the Initial Request Handlers in the main.go File in the partyinvites Folder

The functionality for dealing with HTTP requests is defined in the net/http package, which is part of the Go standard library. Functions that process requests must have a specific combination of parameters, like this:
...
func welcomeHandler(writer http.ResponseWriter, request *http.Request) {
...

The second argument is a pointer to an instance of the Request struct, defined in the net/http package, which describes the request being processed. The first argument is an example of an interface, which is why it isn’t defined as a pointer. Interfaces specify a set of methods that any struct type can implement, allowing code to be written to make use of any type that implements those methods, which I explain in detail in Chapter 11.

One of the most commonly used interfaces is Writer, which is used everywhere data can be written, such as files, strings, and network connections. The ResponseWriter type adds additional features that are specific to dealing with HTTP responses.

Go has a clever, if unusual, approach to interfaces and abstraction, the consequence of which is that the ResponseWriter that is received by the functions defined in Listing 1-16 can be used by any code that knows how to write data using the Writer interface. This includes the Execute method defined by the *Template type that I created when loading the templates, making it easy to use the output from rendering a template in an HTTP response:
...
templates["list"].Execute(writer, responses)
...

This statement reads the *template.Template from the map assigned to the templates variable and invokes the Execute method it defines. The first argument is the ResponseWriter, which is where the output from the response will be written, and the second argument is a data value that can be used in the expressions contained in the template.

The net/http package defines the HandleFunc function, which is used to specify a URL path and the handler that will receive matching requests. I used HandleFunc to register my new handler functions so they will respond to the / and /list URL paths:
...
http.HandleFunc("/", welcomeHandler)
http.HandleFunc("/list", listHandler)
...
I demonstrate how the request dispatch process can be customized in later chapters, but the standard library contains a basic URL routing system that will match incoming requests and pass them onto the handler function for processing. I have not defined all the handler functions needed by the application, but there is enough functionality in place to start processing requests with an HTTP server, as shown in Listing 1-17.
package main
import (
    "fmt"
    "html/template"
    "net/http"
)
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
var templates = make(map[string]*template.Template, 3)
func loadTemplates() {
    templateNames := [5]string { "welcome", "form", "thanks", "sorry", "list" }
    for index, name := range templateNames {
        t, err := template.ParseFiles("layout.html", name + ".html")
        if (err == nil) {
            templates[name] = t
            fmt.Println("Loaded template", index, name)
        } else {
            panic(err)
        }
    }
}
func welcomeHandler(writer http.ResponseWriter, request *http.Request) {
    templates["welcome"].Execute(writer, nil)
}
func listHandler(writer http.ResponseWriter, request *http.Request) {
    templates["list"].Execute(writer, responses)
}
func main() {
    loadTemplates()
    http.HandleFunc("/", welcomeHandler)
    http.HandleFunc("/list", listHandler)
    err := http.ListenAndServe(":5000", nil)
    if (err != nil) {
        fmt.Println(err)
    }
}
Listing 1-17

Creating an HTTP Server in the main.go File in the partyinvites Folder

The new statements create an HTTP server that listens for requests on port 5000, which is specified by the first argument to the ListenAndServe function. The second argument is nil, which tells the server that requests should be processed using the functions registered with the HandleFunc function. Run the command shown in Listing 1-18 in the partyinvites folder to compile and execute the project.
go run .
Listing 1-18

Compiling and Executing the Project

Open a new web browser and request the URL http://localhost:5000, which will produce the response shown in Figure 1-1. (If you are using Windows, you may be prompted for approval by the Windows firewall before requests can be processed by the server. You will need to grant approval every time you use the go run . command in this chapter. Later chapters introduce a simple PowerShell script to address this issue.)
Figure 1-1

Handling HTTP requests

Press Ctrl+C to stop the application once you have confirmed that it can produce a response.

Writing the Form Handling Function

Clicking the RSVP Now button has no effect because there is no handler for the /form URL that it targets. Listing 1-19 defines the new handler function and starts implementing the features the application requires.
package main
import (
    "fmt"
    "html/template"
    "net/http"
)
type Rsvp struct {
    Name, Email, Phone string
    WillAttend bool
}
var responses = make([]*Rsvp, 0, 10)
var templates = make(map[string]*template.Template, 3)
func loadTemplates() {
    templateNames := [5]string { "welcome", "form", "thanks", "sorry", "list" }
    for index, name := range templateNames {
        t, err := template.ParseFiles("layout.html", name + ".html")
        if (err == nil) {
            templates[name] = t
            fmt.Println("Loaded template", index, name)
        } else {
            panic(err)
        }
    }
}
func welcomeHandler(writer http.ResponseWriter, request *http.Request) {
    templates["welcome"].Execute(writer, nil)
}
func listHandler(writer http.ResponseWriter, request *http.Request) {
    templates["list"].Execute(writer, responses)
}
type formData struct {
    *Rsvp
    Errors []string
}
func formHandler(writer http.ResponseWriter, request *http.Request) {
    if request.Method == http.MethodGet {
        templates["form"].Execute(writer, formData {
            Rsvp: &Rsvp{}, Errors: []string {},
        })
    }
}
func main() {
    loadTemplates()
    http.HandleFunc("/", welcomeHandler)
    http.HandleFunc("/list", listHandler)
    http.HandleFunc("/form", formHandler)
    err := http.ListenAndServe(":5000", nil)
    if (err != nil) {
        fmt.Println(err)
    }
}
Listing 1-19

Adding the Form Handler Function in the main.go File in the partyinvites Folder

The form.html template expects to receive a specific data structure of data values to render its content. To represent this structure, I have defined a new struct type named formData. Go structs can be more than just a group of name-value fields, and one feature they provide is support for creating new structs using existing structs. In this case, I have defined the formData struct using a pointer to the existing Rsvp struct, like this:
...
type formData struct {
    *Rsvp
    Errors []string
}
...

The result is that the formData struct can be used as though it defines the Name, Email, Phone, and WillAttend fields from the Rsvp struct and that I can create an instance of the formData struct using an existing Rsvp value. The asterisk denotes a pointer, which means that I don’t want to copy the Rsvp value when I create the formData value.

The new handler function checks the value of the request.Method field, which returns the type of HTTP request that has been received. For GET requests, the form template is executed, like this:
...
if request.Method == http.MethodGet {
    templates["form"].Execute(writer, formData {
        Rsvp: &Rsvp{}, Errors: []string {},
    })
...
There is no data to use when responding to GET requests, but I need to provide the template with the expected data structure. To do this, I create an instance of the formData struct using the default values for its fields:
...
templates["form"].Execute(writer, formData {
        Rsvp: &Rsvp{}, Errors: []string {},
    })
...
Go doesn’t have a new keyword, and values are created using braces, with default values being used for any field for which a value is not specified. This kind of statement can be hard to parse at first, but it creates a formData struct by creating a new instance of the Rsvp struct and creating a string slice that contains no values. The ampersand (the & character) creates a pointer to a value:
...
templates["form"].Execute(writer, formData {
        Rsvp: &Rsvp{}, Errors: []string {},
    })
...
The formData struct has been defined to expect a pointer to an Rsvp value, which the ampersand allows me to create. Run the command shown in Listing 1-20 in the partyinvites folder to compile and execute the project.
go run .
Listing 1-20

Compiling and Executing the Project

Open a new web browser, request the URL http://localhost:5000, and click the RSVP Now button. The new handler will receive the request from the browser and display the HTML form shown in Figure 1-2.
Figure 1-2

Displaying the HTML form

Handling the Form Data

Now I need to handle POST requests and read the data that the user has entered into the form, as shown in Listing 1-21. This listing shows only the changes to the formHandler function; the rest of the main.go file remains unchanged.
...
func formHandler(writer http.ResponseWriter, request *http.Request) {
    if request.Method == http.MethodGet {
        templates["form"].Execute(writer, formData {
            Rsvp: &Rsvp{}, Errors: []string {},
        })
    } else if request.Method == http.MethodPost {
        request.ParseForm()
        responseData := Rsvp {
            Name: request.Form["name"][0],
            Email: request.Form["email"][0],
            Phone: request.Form["phone"][0],
            WillAttend: request.Form["willattend"][0] == "true",
        }
        responses = append(responses, &responseData)
        if responseData.WillAttend {
            templates["thanks"].Execute(writer, responseData.Name)
        } else {
            templates["sorry"].Execute(writer, responseData.Name)
        }
    }
}
...
Listing 1-21

Handling Form Data in the main.go File in the partyinvites Folder

The ParseForm method processes the form data contained in an HTTP request and populates a map, which can be accessed through the Form field. The form data is then used to create an Rsvp value:
...
responseData := Rsvp {
    Name: request.Form["name"][0],
    Email: request.Form["email"][0],
    Phone: request.Form["phone"][0],
    WillAttend: request.Form["willattend"][0] == "true",
}
...

This statement demonstrates how a struct is instantiated with values for its fields, as opposed to the default values that were used in Listing 1-19. HTML forms can include multiple values with the same name, so the form data is presented as a slice of values. I know that there will be only one value for each name, and I access the first value in the slice using the standard zero-based index notation that most languages use.

Once I have created an Rsvp value, I add it to the slice assigned to the responses variable:
...
responses = append(responses, &responseData)
...

The append function is used to append a value to a slice. Notice that I use the ampersand to create a pointer to the Rsvp value I created. If I had not used a pointer, then my Rsvp value would be duplicated when it is added to the slice.

The remaining statements use the value of the WillAttend field to select the template that will be presented to the user.

Run the command shown in Listing 1-22 in the partyinvites folder to compile and execute the project.
go run .
Listing 1-22

Compiling and Executing the Project

Open a new web browser, request the URL http://localhost:5000, and click the RSVP Now button. Fill out the form and click the Submit RSVP button; you will receive a response selected based on which value you chose using the HTML select element. Click the link in the response to see a summary of the responses the application has received, as shown in Figure 1-3.
Figure 1-3

Processing form data

Adding Data Validation

All that’s required to complete the application is some basic validation to ensure the user has filled out the form, as shown in Listing 1-23. This listing shows the changes to the formHandler function, and the rest of the main.go file remains unchanged.
...
func formHandler(writer http.ResponseWriter, request *http.Request) {
    if request.Method == http.MethodGet {
        templates["form"].Execute(writer, formData {
            Rsvp: &Rsvp{}, Errors: []string {},
        })
    } else if request.Method == http.MethodPost {
        request.ParseForm()
        responseData := Rsvp {
            Name: request.Form["name"][0],
            Email: request.Form["email"][0],
            Phone: request.Form["phone"][0],
            WillAttend: request.Form["willattend"][0] == "true",
        }
        errors := []string {}
        if responseData.Name == "" {
            errors = append(errors, "Please enter your name")
        }
        if responseData.Email == "" {
            errors = append(errors, "Please enter your email address")
        }
        if responseData.Phone == "" {
            errors = append(errors, "Please enter your phone number")
        }
        if len(errors) > 0 {
            templates["form"].Execute(writer, formData {
                Rsvp: &responseData, Errors: errors,
            })
        } else {
            responses = append(responses, &responseData)
            if responseData.WillAttend {
                templates["thanks"].Execute(writer, responseData.Name)
            } else {
                templates["sorry"].Execute(writer, responseData.Name)
            }
        }
    }
}
...
Listing 1-23

Checking the Form Data in the main.go File in the partyinvites Folder

The application will receive an empty string ("") from the request when the user doesn’t provide a value for a form field. The new statements in Listing 1-23 check the Name, Email, and Phone fields and add a message to a slice of strings for each field that doesn’t have a value. I use the built-in len function to get the number of values in the errors slice, and if there are errors, then I render the contents of the form template again, including the error messages in the data the template receives. If there are no errors, then the thanks or sorry template is used.

Run the command shown in Listing 1-24 in the partyinvites folder to compile and execute the project.
go run .
Listing 1-24

Compiling and Executing the Project

Open a new web browser, request the URL http://localhost:5000, and click the RSVP Now button. Click the Submit RSVP button without entering any values into the form; you will see warning messages, as shown in Figure 1-4. Enter some details into the form and submit it again, and you will see the final message.
Figure 1-4

Validating data

Summary

In this chapter, I installed the Go package and used the tools it contains to create a simple web application using only a single code file and some basic HTML templates. Now that you have seen Go in action, the next chapter puts this book into context.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.96.5