You're reading a sample of this book. Get the full version here.
Let's Go Foundations › Project Structure and Organization
Previous · Contents · Next
Chapter 2.7.

Project Structure and Organization

Before we add any more code to our main.go file it’s a good time to think how to organize and structure this project.

It’s important to explain upfront that there’s no single right — or even recommended — way to structure web applications in Go. And that’s both good and bad. It means that you have freedom and flexibility over how you organize your code, but it’s also easy to get stuck down a rabbit-hole of uncertainty when trying to decide what the best structure should be.

As you gain experience with Go, you’ll get a feel for which patterns work well for you in different situations. But as a starting point, the best advice I can give you is don’t over-complicate things. Try hard to add structure and complexity only when it’s demonstrably needed.

For this project we’ll implement an outline structure which follows a popular and tried-and-tested approach. It’s a solid starting point, and you should be able to reuse the general structure in a wide variety of projects.

If you’re following along, make sure that you’re in the root of your project repository and run the following commands:

$ cd $HOME/code/snippetbox
$ rm main.go
$ mkdir -p cmd/web pkg ui/html ui/static
$ touch cmd/web/main.go
$ touch cmd/web/handlers.go

The structure of your project repository should now look like this:

02.07-01.png

Let’s take a moment to discuss what each of these directories will be used for.

So why are we using this structure?

There are two big benefits:

  1. It gives a clean separation between Go and non-Go assets. All the Go code we write will live exclusively under the cmd and pkg directories, leaving the project root free to hold non-Go assets like UI files, makefiles and module definitions (including our go.mod file). This can make things easier to manage when it comes to building and deploying your application in the future.

  2. It scales really nicely if you want to add another executable application to your project. For example, you might want to add a CLI (Command Line Interface) to automate some administrative tasks in the future. With this structure, you could create this CLI application under cmd/cli and it will be able to import and reuse all the code you’ve written under the pkg directory.

Refactoring Your Existing Code

Let’s quickly port the code we’ve already written to use this new structure.

File: cmd/web/main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", home)
    mux.HandleFunc("/snippet", showSnippet)
    mux.HandleFunc("/snippet/create", createSnippet)

    log.Println("Starting server on :4000")
    err := http.ListenAndServe(":4000", mux)
    log.Fatal(err)
}
File: cmd/web/handlers.go
package main

import (
    "fmt"
    "net/http"
    "strconv"
)

func home(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    w.Write([]byte("Hello from Snippetbox"))
}

func showSnippet(w http.ResponseWriter, r *http.Request) {
    id, err := strconv.Atoi(r.URL.Query().Get("id"))
    if err != nil || id < 1 {
        http.NotFound(w, r)
        return
    }

    fmt.Fprintf(w, "Display a specific snippet with ID %d...", id)
}

func createSnippet(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        w.Header().Set("Allow", "POST")
        http.Error(w, "Method Not Allowed", 405)
        return
    }

    w.Write([]byte("Create a new snippet..."))
}

So now our web application consists of multiple Go source code files under the cmd/web directory. To run these, we can use the go run command like so:

$ cd $HOME/code/snippetbox
$ go run ./cmd/web
2018/08/02 17:24:28 Starting server on :4000