📄 Page
1
(This page has no text content)
📄 Page
2
Let’s Go teaches you step-by-step how to create fast, secure and maintainable web applications using the fantastic programming language Go. The idea behind this book is to help you learn by doing. Together we’ll walk through the start- to-finish build of a web application — from structuring your workspace, through to session management, authenticating users, securing your server and testing your application. Building a complete web application in this way has several benefits. It helps put the things you’re learning into context, it demonstrates how different parts of your codebase link together, and it forces us to work through the edge-cases and difficulties that come up when writing software in real-life. In essence, you’ll learn more than you would by just reading Go’s (great) documentation or standalone blog posts. By the end of the book you’ll have the understanding — and confidence — to build your own production-ready web applications with Go. Although you can read this book cover-to-cover, it’s designed specifically so you can follow along with the project build yourself. Break out your text editor, and happy coding! — Alex
📄 Page
3
Contents 1. Introduction 1.1. Prerequisites 2. Foundations 2.1. Project setup and creating a module 2.2. Web application basics 2.3. Routing requests 2.4. Customizing HTTP headers 2.5. URL query strings 2.6. Project structure and organization 2.7. HTML templating and inheritance 2.8. Serving static files 2.9. The http.Handler interface 3. Configuration and error handling 3.1. Managing configuration settings 3.2. Leveled logging 3.3. Dependency injection 3.4. Centralized error handling 3.5. Isolating the application routes 4. Database-driven responses 4.1. Setting up MySQL 4.2. Installing a database driver 4.3. Modules and reproducible builds 4.4. Creating a database connection pool 4.5. Designing a database model 4.6. Executing SQL statements 4.7. Single-record SQL queries 4.8. Multiple-record SQL queries 4.9. Transactions and other details 5. Dynamic HTML templates 5.1. Displaying dynamic data 5.2. Template actions and functions
📄 Page
4
5.3. Caching templates 5.4. Catching runtime errors 5.5. Common dynamic data 5.6. Custom template functions 6. Middleware 6.1. How middleware works 6.2. Setting security headers 6.3. Request logging 6.4. Panic recovery 6.5. Composable middleware chains 7. Advanced routing 7.1. Choosing a router 7.2. Clean URLs and method-based routing 8. Processing forms 8.1. Setting up a HTML form 8.2. Parsing form data 8.3. Validating form data 8.4. Displaying errors and repopulating fields 8.5. Creating validation helpers 8.6. Automatic form parsing 9. Stateful HTTP 9.1. Choosing a session manager 9.2. Setting up the session manager 9.3. Working with session data 10. Security improvements 10.1. Generating a self-signed TLS certificate 10.2. Running a HTTPS server 10.3. Configuring HTTPS settings 10.4. Connection timeouts 11. User authentication 11.1. Routes setup 11.2. Creating a users model 11.3. User signup and password encryption 11.4. User login 11.5. User logout
📄 Page
5
11.6. User authorization 11.7. CSRF protection 12. Using request context 12.1. How request context works 12.2. Request context for authentication/authorization 13. Optional Go features 13.1. Using embedded files 13.2. Using generics 14. Testing 14.1. Unit testing and sub-tests 14.2. Testing HTTP handlers and middleware 14.3. End-to-end testing 14.4. Customizing how tests run 14.5. Mocking dependencies 14.6. Testing HTML forms 14.7. Integration testing 14.8. Profiling test coverage 15. Conclusion 16. Further reading and useful links 17. Guided exercises 17.1. Add an 'About' page to the application 17.2. Add a debug mode 17.3. Test the snippetCreate handler 17.4. Add an 'Account' page to the application 17.5. Redirect user appropriately after login 17.6. Implement a 'Change Password' feature
📄 Page
6
Chapter 1. Introduction In this book we’ll be building a web application called Snippetbox, which lets people paste and share snippets of text — a bit like Pastebin or GitHub’s Gists. Towards the end of the build it will look a bit like this: Our application will start off super simple, with just one web page. Then with each chapter we’ll build it up step-by-step until a user is able save and view snippets via the app. This will take us through topics like how to structure a project, routing requests, working with a database, processing forms and displaying dynamic data safely. Then later in the book we’ll add user accounts, and restrict the application so that only registered users can create snippets. This will take us through more advanced topics like configuring a HTTPS server, session management, user authentication and middleware.
📄 Page
7
Conventions Throughout this book code blocks are shown with a silver background like below. If the code is particularly long, parts that aren’t relevant may be replaced with an ellipsis. To make it easy to follow along, most code blocks also have a title bar at the top indicating the name of the file that we’re working on. File: hello.go package main ... // Indicates that some existing code has been omitted. func sayHello() { fmt.Println("Hello world!") } Terminal (command line) instructions are shown with a black background and start with a dollar symbol. These commands should work on any Unix-based operating system, including Mac OSX and Linux. Sample output is shown in silver beneath the command, like so: $ echo "Hello world!" Hello world! If you’re using Windows, you should replace the command with the DOS equivalent or carry out the action via the normal Windows GUI. Some chapters in this book end with an additional information section. These sections contain information that isn’t relevant to our application build, but is still important (or sometimes, just interesting) to know about. If you’re very new to Go, you might want to skip these parts and circle back to them later. Hint: If you’re following along with the application build I recommend using the HTML version of this book instead of the PDF or EPUB. The HTML version works in all browsers, and the proper formatting of code blocks is retained if you want to copy-and-paste code directly from the book. About the author Hey, I’m Alex Edwards, a full-stack web developer and author. I live near Innsbruck, Austria.
📄 Page
8
I’ve been working with Go for over 8 years, building production applications for myself and commercial clients, and helping people all around the world improve their Go skills. You can see more of my writing on my blog (where I publish detailed tutorials), some of my open-source work on GitHub, and you can also follow me on Instagram and Twitter. Copyright and disclaimer Let’s Go: Learn to build professional web applications with Go. Copyright © 2023 Alex Edwards. Last updated 2023-02-17 14:27:59 UTC. Version 2.20.0. The Go gopher was designed by Renee French and is used under the Creative Commons 3.0 Attributions license. Cover gopher adapted from vectors by Egon Elbre. The information provided within this book is for general informational purposes only. While the author and publisher have made every effort to ensure the accuracy of the information within this book was correct at time of publication there are no representations or warranties, express or implied, about the completeness, accuracy, reliability, suitability or availability with respect to the information, products, services, or related graphics contained in this book for any purpose. Any use of this information is at your own risk.
📄 Page
9
Chapter 1.1. Prerequisites Background knowledge This book is designed for people who are new to Go, but you’ll probably find it more enjoyable if you have a general understanding of Go’s syntax first. If you find yourself struggling with the syntax, the Little Book of Go by Karl Seguin is a fantastic tutorial, or if you want something more interactive I recommend running through the Tour of Go. I’ve also assumed that you’ve got a (very) basic understanding of HTML/CSS and SQL, and some familiarity with using your terminal (or command line for Windows users). If you’ve built a web application in any other language before — whether it’s Ruby, Python, PHP or C# — then this book should be a good fit for you. Go 1.20 The information in this book is correct for the latest major release of Go (version 1.20), and you should install this if you’d like to code-along with the application build. If you’ve already got Go installed, you can check the version number from your terminal by using the go version command. The output should look similar to this: $ go version go version go1.20 linux/amd64 If you need to upgrade your version of Go — or install Go from scratch — then please go ahead and do that now. Detailed instructions for different operating systems can be found here: Removing an old version of Go Installing Go on Mac OS X Installing Go on Windows Installing Go on Linux Other software There are a few other bits of software that you should make sure are available on your
📄 Page
10
computer if you want to follow along fully. They are: The curl tool for working with HTTP requests and responses from your terminal. On MacOS and Linux machines it should be pre-installed or available in your software repositories. Otherwise, you can download the latest version from here. A web browser with good developer tools. I’ll be using Firefox in this book, but Chromium, Chrome or Microsoft Edge will work too. Your favorite text editor
📄 Page
11
Chapter 2. Foundations Alright, let’s get started! In this first section of the book we’re going to lay the groundwork for our project and explain the main principles that you need to know for the rest of the application build. You’ll learn how to: Setup a project directory which follows the Go conventions. Start a web server and listen for incoming HTTP requests. Route requests to different handlers based on the request path. Send different HTTP responses, headers and status codes to users. Fetch and validate untrusted user input from URL query string parameters. Structure your project in a sensible and scalable way. Render HTML pages and use template inheritance to keep your markup free of duplicate boilerplate code. Serve static files like images, CSS and JavaScript from your application.
📄 Page
12
Chapter 2.1. Project setup and creating a module Before we write any code, you’ll need to create a snippetbox directory on your computer to act as the top-level ‘home’ for this project. All the Go code we write throughout the book will live in here, along with other project-specific assets like HTML templates and CSS files. So, if you’re following along, open your terminal and create a new project directory called snippetbox anywhere on your computer. I’m going to locate my project directory under $HOME/code, but you can choose a different location if you wish. $ mkdir -p $HOME/code/snippetbox Creating a module The next thing you need to do is choose a module path for your project. If you’re not already familiar with Go modules, you can think of a module path as basically being a canonical name or identifier for your project. You can pick almost any string as your module path, but the important thing to focus on is uniqueness. To avoid potential import conflicts with other people’s projects or the standard library in the future, you want to pick a module path that is globally unique and unlikely to be used by anything else. In the Go community, a common convention is to base your module paths on a URL that you own. In my case, a clear, succinct and unlikely-to-be-used-by-anything-else module path for this project would be snippetbox.alexedwards.net, and I’ll use this throughout the rest of the book. If possible, you should swap this for something that’s unique to you instead. Now that we’ve decided a unique module path, the next thing that we need to do is turn our project directory into a module. Make sure that you’re in the root of the directory and then run the go mod init command — passing in your module path as a parameter like so: $ cd $HOME/code/snippetbox $ go mod init snippetbox.alexedwards.net go: creating new go.mod: module snippetbox.alexedwards.net
📄 Page
13
At this point your project directory should look a bit like the screenshot below. Notice the go.mod file which has been created? At the moment there’s not much going on in this file, and if you open it up in your text editor it should look like this (but preferably with your own unique module path instead): File: go.mod module snippetbox.alexedwards.net go 1.20 We’ll talk about modules in more detail later in the book, but for now it’s enough to know that when there is a valid go.mod file in the root of your project directory, your project is a module. Setting up your project as a module has a number of advantages — including making it much easier to manage third-party dependencies, avoid supply-chain attacks, and ensure reproducible builds of your application in the future. Hello world! Before we continue, let’s quickly check that everything is set up correctly. Go ahead and create a new main.go in your project directory containing the following code:
📄 Page
14
$ touch main.go File: main.go package main import "fmt" func main() { fmt.Println("Hello world!") } Save this file, then use the go run . command in your terminal to compile and execute the code in the current directory. All being well, you will see the following output: $ go run . Hello world! Additional information Module paths for downloadable packages If you’re creating a project which can be downloaded and used by other people and programs, then it’s good practice for your module path to equal the location that the code can be downloaded from. For instance, if your package is hosted at https://github.com/foo/bar then the module path for the project should be github.com/foo/bar.
📄 Page
15
Chapter 2.2. Web application basics Now that everything is set up correctly let’s make the first iteration of our web application. We’ll begin with the three absolute essentials: The first thing we need is a handler. If you’re coming from an MVC-background, you can think of handlers as being a bit like controllers. They’re responsible for executing your application logic and for writing HTTP response headers and bodies. The second component is a router (or servemux in Go terminology). This stores a mapping between the URL patterns for your application and the corresponding handlers. Usually you have one servemux for your application containing all your routes. The last thing we need is a web server. One of the great things about Go is that you can establish a web server and listen for incoming requests as part of your application itself. You don’t need an external third-party server like Nginx or Apache. Let’s put these components together in the main.go file to make a working application. File: main.go package main import ( "log" "net/http" ) // Define a home handler function which writes a byte slice containing // "Hello from Snippetbox" as the response body. func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from Snippetbox")) } func main() { // Use the http.NewServeMux() function to initialize a new servemux, then // register the home function as the handler for the "/" URL pattern. mux := http.NewServeMux() mux.HandleFunc("/", home) // Use the http.ListenAndServe() function to start a new web server. We pass in // two parameters: the TCP network address to listen on (in this case ":4000") // and the servemux we just created. If http.ListenAndServe() returns an error // we use the log.Fatal() function to log the error message and exit. Note // that any error returned by http.ListenAndServe() is always non-nil. log.Print("Starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
📄 Page
16
Note: The home handler function is just a regular Go function with two parameters. The http.ResponseWriter parameter provides methods for assembling a HTTP response and sending it to the user, and the *http.Request parameter is a pointer to a struct which holds information about the current request (like the HTTP method and the URL being requested). We’ll talk more about these parameters and demonstrate how to use them as we progress through the book. When you run this code, it should start a web server listening on port 4000 of your local machine. Each time the server receives a new HTTP request it will pass the request on to the servemux and — in turn — the servemux will check the URL path and dispatch the request to the matching handler. Let’s give this a whirl. Save your main.go file and then try running it from your terminal using the go run command. $ cd $HOME/code/snippetbox $ go run . 2022/01/29 11:13:26 Starting server on :4000 While the server is running, open a web browser and try visiting http://localhost:4000 . If everything has gone to plan you should see a page which looks a bit like this:
📄 Page
17
Important: Before we continue, I should explain that Go’s servemux treats the URL pattern "/" like a catch-all. So at the moment all HTTP requests to our server will be handled by the home function, regardless of their URL path. For instance, you can visit a different URL path like http://localhost:4000/foo and you’ll receive exactly the same response. If you head back to your terminal window, you can stop the server by pressing Ctrl+c on your keyboard. Additional information Network addresses The TCP network address that you pass to http.ListenAndServe() should be in the format "host:port" . If you omit the host (like we did with ":4000") then the server will listen on all your computer’s available network interfaces. Generally, you only need to specify a host in the address if your computer has multiple network interfaces and you want to listen on just one of them. In other Go projects or documentation you might sometimes see network addresses written using named ports like ":http" or ":http-alt" instead of a number. If you use a named port then Go will attempt to look up the relevant port number from your /etc/services file when starting the server, or will return an error if a match can’t be found. Using go run During development the go run command is a convenient way to try out your code. It’s essentially a shortcut that compiles your code, creates an executable binary in your /tmp directory, and then runs this binary in one step. It accepts either a space-separated list of .go files, the path to a specific package (where the . character represents your current directory), or the full module path. For our application at the moment, the three following commands are all equivalent: $ go run . $ go run main.go $ go run snippetbox.alexedwards.net
📄 Page
18
Chapter 2.3. Routing requests Having a web application with just one route isn’t very exciting… or useful! Let’s add a couple more routes so that the application starts to shape up like this: URL Pattern Handler Action / home Display the home page /snippet/view snippetView Display a specific snippet /snippet/create snippetCreate Create a new snippet Reopen the main.go file and update it as follows: File: main.go package main import ( "log" "net/http" ) func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from Snippetbox")) } // Add a snippetView handler function. func snippetView(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Display a specific snippet...")) } // Add a snippetCreate handler function. func snippetCreate(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Create a new snippet...")) } func main() { // Register the two new handler functions and corresponding URL patterns with // the servemux, in exactly the same way that we did before. mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet/view", snippetView) mux.HandleFunc("/snippet/create", snippetCreate) log.Print("Starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
📄 Page
19
Make sure these changes are saved and then restart the web application: $ cd $HOME/code/snippetbox $ go run . 2022/01/29 11:16:43 Starting server on :4000 If you visit the following links in your web browser you should now get the appropriate response for each route: http://localhost:4000/snippet/view http://localhost:4000/snippet/create
📄 Page
20
Fixed path and subtree patterns Now that the two new routes are up and running let’s talk a bit of theory. Go’s servemux supports two different types of URL patterns: fixed paths and subtree paths. Fixed paths don’t end with a trailing slash, whereas subtree paths do end with a trailing slash. Our two new patterns — "/snippet/view" and "/snippet/create" — are both examples of fixed paths. In Go’s servemux, fixed path patterns like these are only matched (and the corresponding handler called) when the request URL path exactly matches the fixed path. In contrast, our pattern "/" is an example of a subtree path (because it ends in a trailing slash). Another example would be something like "/static/". Subtree path patterns are matched (and the corresponding handler called) whenever the start of a request URL path matches the subtree path. If it helps your understanding, you can think of subtree paths as acting a bit like they have a wildcard at the end, like "/**" or "/static/**". This helps explain why the "/" pattern is acting like a catch-all. The pattern essentially means match a single slash, followed by anything (or nothing at all) . Restricting the root url pattern