(This page has no text content)
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
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. Wildcard route patterns 2.5. Method-based routing 2.6. Customizing responses 2.7. Project structure and organization 2.8. HTML templating and inheritance 2.9. Serving static files 2.10. The http.Handler interface 3. Configuration and error handling 3.1. Managing configuration settings 3.2. Structured 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 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 common headers 6.3. Request logging 6.4. Panic recovery 6.5. Composable middleware chains 7. Processing forms 7.1. Setting up an HTML form 7.2. Parsing form data 7.3. Validating form data 7.4. Displaying errors and repopulating fields 7.5. Creating validation helpers 7.6. Automatic form parsing 8. Stateful HTTP 8.1. Choosing a session manager 8.2. Setting up the session manager 8.3. Working with session data 9. Server and security improvements 9.1. The http.Server struct 9.2. The server error log 9.3. Generating a self-signed TLS certificate 9.4. Running a HTTPS server 9.5. Configuring HTTPS settings 9.6. Connection timeouts 10. User authentication 10.1. Routes setup 10.2. Creating a users model 10.3. User signup and password encryption 10.4. User login
10.5. User logout 10.6. User authorization 10.7. CSRF protection 11. Using request context 11.1. How request context works 11.2. Request context for authentication/authorization 12. File embedding 12.1. Embedding static files 12.2. Embedding HTML templates 13. Testing 13.1. Unit testing and sub-tests 13.2. Testing HTTP handlers and middleware 13.3. End-to-end testing 13.4. Customizing how tests run 13.5. Mocking dependencies 13.6. Testing HTML forms 13.7. Integration testing 13.8. Profiling test coverage 14. Conclusion 15. Further reading and useful links 16. Guided exercises 16.1. Add an 'About' page to the application 16.2. Add a debug mode 16.3. Test the snippetCreate handler 16.4. Add an 'Account' page to the application 16.5. Redirect user appropriately after login 16.6. Implement a 'Change Password' feature
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 can 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.
Conventions Code blocks in this book are shown with a silver background, like the example below. If a code block is particularly long, any 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 the code is in. Like this: 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. Please note that the dates and timestamps shown in screenshots and the example output from commands are illustrative only. They do not necessarily align with each other, or progress chronologically throughout the book. 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. When using the HTML version, you can also use the left and right arrow keys on your keyboard to navigate between chapters. About the author Hey, I’m Alex Edwards, a full-stack web developer and author. I live near Innsbruck, Austria. I’ve been working with Go for over 10 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 © 2025 Alex Edwards. Last updated 2025-02-13 15:56:01 UTC. Version 2.24.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.
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.24 The information in this book is correct for the latest major release of Go (version 1.24), 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.24.0 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 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
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: Set up 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 and method. Use wildcard segments in your routing patterns. Send different HTTP responses, headers and status codes to users. Structure your project in a sensible and scalable way. Render HTML pages and use template inheritance to keep your HTML markup free of duplicate boilerplate code. Serve static files like images, CSS and JavaScript from your application.
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 decide on 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. Once you’ve decided on a unique module path, the next thing you need to do is turn your project directory into a module. Make sure that you’re in the root of your project directory and then run the go mod init command — passing in your chosen 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
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.24.0 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:
$ 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.
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’ve previously built web applications using a MVC pattern, 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 routing 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, Apache or Caddy. 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) // Print a log message to say that the server is starting. log.Print("starting server on :4000") // 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. err := http.ListenAndServe(":4000", mux) log.Fatal(err) } 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 will 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 . 2024/03/18 11:29:23 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: Important: Before we continue, I should explain that Go’s servemux treats the route 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/bar 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 the http.ListenAndServe() function will attempt to look up the relevant port number from your /etc/services file when starting the server, returning 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
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: Route pattern Handler Action / home Display the home page /snippet/view snippetView Display a specific snippet /snippet/create snippetCreate Display a form for creating 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("Display a form for creating a new snippet...")) } func main() { // Register the two new handler functions and corresponding route 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) }
Make sure these changes are saved and then restart the web application: $ cd $HOME/code/snippetbox $ go run . 2024/03/18 11:29:23 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
Comments 0
Loading comments...
Reply to Comment
Edit Comment