Go for Pythonistas: Building Web Apps with Go
Section 3 of 13

Setting Up Your Go Development Environment

You're ready to write Go. Before you can, you need the language installed and a place to put your code. This section is the shortest in the course, because Go's toolchain genuinely is simpler than Python's — but understanding why it's simpler will teach you something about how Go thinks.

Installing Go

Head to golang.org/dl, grab the installer for your OS, run it, and you're done. On macOS and Linux it lands in /usr/local/go; on Windows, C:\Program Files\Go. PATH gets updated automatically.

macOS with Homebrew:

brew install go

Linux:

Download the tarball directly from go.dev/dl and follow the official instructions. It's four commands and takes two minutes.

Ubuntu/Debian users: Don't use apt install golang-go. The Ubuntu package repository typically ships Go 1–2 major versions behind current — on Ubuntu 22.04 LTS, it installs Go 1.18 instead of 1.22. This matters because Go 1.22 introduced the new ServeMux routing syntax that later sections of this course cover explicitly. If you install via apt and go version shows anything older than 1.21, uninstall it and use the official tarball instead.

After installation, verify it worked:

go version  # go version go1.22.0 linux/amd64 (or whatever's current)

That's genuinely it. One Go installation that works everywhere on your machine. No virtual environments. No pyenv vs. system vs. homebrew confusion. Just one binary handling every project you touch.

GOPATH vs. Modules: Which Tutorial Are You Reading?

Before we go further, there's a gotcha worth naming directly, because it's going to bite you the moment you start googling.

Go used to organize code around a special directory called $GOPATH — typically ~/go — where all your Go code and dependencies lived together in a specific structure. Pre-2019 tutorials, StackOverflow answers, and a surprising amount of still-indexed documentation describe this system. You'll see instructions to put code in $GOPATH/src/github.com/yourname/yourproject, and explanations of how go get fetches packages into your GOPATH workspace.

Ignore all of it. As of Go 1.11, Go introduced modules as the replacement, and as of Go 1.16 they became the default. The GOPATH system is legacy. Modern Go projects use go.mod files (covered in the next section) and live wherever you want on your filesystem — same as any other project.

The practical rule: if a tutorial tells you to put your code inside $GOPATH/src/, close that tab. If it references GOPATH as a required setup step rather than a historical footnote, it's outdated. You won't need GOPATH for anything in this course.

Editor Setup: Just Use VS Code

You can use any editor. GoLand (JetBrains' dedicated Go IDE) is excellent if you're already living in the JetBrains ecosystem. Vim or Neovim with the right plugins work great if that's your world. But for Python developers making the switch, VS Code with the Go extension is the straightforward choice.

  1. Install VS Code if you don't have it
  2. Install the Go extension (published by Google, extension ID: golang.go)
  3. Open any .go file, and VS Code will ask if you want to install the Go tools it needs

Say yes to everything in that last step. It installs gopls (the language server), dlv (the debugger), staticcheck (the linter), and a handful of others — all in the background, all at once. Thirty seconds later, you've got autocomplete, inline error checking, go-to-definition, and automatic formatting including goimports, which handles import management as well as formatting.

The experience is noticeably smooth. Go's tooling was designed from the ground up to be machine-readable, so IDE support feels more consistent than what you're probably used to in Python (where pylance, pyright, and pylsp sometimes behave differently depending on your setup).

Tip: In VS Code settings, set "editor.formatOnSave": true and "[go]": {"editor.defaultFormatter": "golang.go"}. This runs format-on-save with goimports — which both formats your code and automatically adds or removes import statements. Future you will be grateful.

Your First Go Program

Let's actually build something. The official getting-started guide walks through this, and it's three steps: make a directory, initialize a module, write code.

mkdir hello-go
cd hello-go
go mod init example/hello

That go mod init command creates a go.mod file — Go's answer to pyproject.toml or requirements.txt. It declares what module this is and will track your dependencies as you accumulate them. The example/hello part is your module path. For real projects, this is usually your repository path (like github.com/yourname/yourproject), but for local tinkering, anything works.

Create a file called main.go:

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Run it:

go run .
# Hello, World!

Three things in there that look different from Python deserve unpacking:

package main — Every Go file starts with a package declaration. Go organizes code into packages (think Python modules, but more rigid). The main package is special: it's the entry point for executables. In Python, any file is runnable — the if __name__ == "__main__" guard is optional convention that you can skip entirely. In Go, only package main files compile to executables, and the compiler enforces this distinction. That means you can never accidentally make a library also an executable. The package system makes the intent explicit, which turns out to be useful when a codebase grows.

import "fmt" — Explicit imports, just like Python. The fmt package (format) is Go's standard library for printing and string formatting. You'll use it constantly.

func main() — Go's entry point. When you run a Go program, this function runs. No configuration, no conditionals, no options — if it's main() in package main, it runs.

The go Command: Your Unified Toolchain

In Python, your tools are scattered across different commands. python to run things. pip to manage dependencies. black or autopep8 to format. mypy for type checking. pytest for tests. You've probably got all of these installed separately, configured differently on different projects.

Go collapses almost all of this into a single go command. As the KDNuggets introduction to Go for Python developers explains, Go's toolchain is integrated in a way that Python's fundamentally isn't. Here's how they map:

Python Tool Go Equivalent What It Does
python script.py go run . Run code directly
pip install go get Add a dependency
pip freeze go mod tidy Reconcile dependencies
black go fmt Format code
mypy go vet Static analysis
pytest go test Run tests
pyinstaller go build Compile to binary

All of these ship with Go. Nothing to install separately. No version conflicts between them. They just work.

Four commands you'll live in constantly:

go run . — Compiles and immediately runs your code. The dot means "the package in the current directory." This is your development mode — when you just want to see if something works. Feels like running a Python script.

go build . — Compiles your code into an actual binary. Linux and Mac get an executable. Windows gets a .exe. This binary has zero runtime dependencies — someone can run it without having Go installed. This matters for deployment.

go fmt ./... — Formats all your Go code to the standard. The ./... means "this directory and everything under it." Run this before you commit, or just let your editor handle it on save.

go vet ./... — Runs static analysis hunting for suspicious code. Catches things like broken format strings, unreachable code, and common mistakes. Think of it as a lightweight, built-in mypy plus flake8.

Try them on your hello-go project:

go build .
./hello  # (or hello.exe on Windows)
# Hello, World!

go fmt ./...
# (no output if already formatted)

go vet ./...
# (no output if no issues found)

go fmt: The Formatting Holy War That Go Ended

Here's something worth thinking about for a moment. Python developers have opinions about formatting. Tabs or spaces. Eighty-nine characters or a hundred twenty. Where the closing bracket goes. Whether black is right or oppressive. Teams have spent actual hours in meetings debating this.

Go made a different choice: the format is specified by the language, enforced by a tool, and completely non-negotiable. go fmt does one thing, formats it one way, and there are zero options to change. When you first encounter this it can feel rigid. Within a week, it feels liberating.

The practical effect: every Go codebase looks identical. Open an unfamiliar Go project and the formatting never requires brain cycles. You'll never read a PR where someone mixed 2-space and 4-space indentation. Code review conversations about formatting simply vanish, because there's nothing to discuss.

Remember: go fmt isn't a style preference — it's the standard. Run it before every commit, or configure your editor to run it automatically. Code that hasn't been gofmt'd is considered incomplete.

If you're not using VS Code, install goimports manually — it does everything go fmt does and also automatically manages your import statements:

go install golang.org/x/tools/cmd/goimports@latest

VS Code with the Go extension already uses goimports under the hood when you enable format-on-save, so you don't need to install it separately there.

Adding an External Package

Before we move on, let's complete the loop: pull in an external dependency. This is the pattern you'll repeat throughout the course, so it's worth experiencing once before we get into the meat of the language.

The official tutorial uses rsc.io/quote as its example, so let's use that:

go get rsc.io/quote

Update main.go:

package main

import (
    "fmt"
    "rsc.io/quote"
)

func main() {
    fmt.Println(quote.Go())
}

Run it:

go run .
# Don't communicate by sharing memory, share memory by communicating.

Now look at your go.mod file:

module example/hello

go 1.22

require rsc.io/quote v1.5.2

There's also a go.sum file — cryptographic hashes of every dependency and all their transitive dependencies. This guarantees that what you download today is exactly what your teammate downloads next week, bit for bit.

go mod tidy

Run go mod tidy periodically to keep your go.mod and go.sum in sync with what you're actually importing. It removes unused dependencies and adds missing ones. Similar in spirit to pip freeze > requirements.txt but smarter — it reads your actual code rather than just listing what's installed.

What You Have Now

Stop for a second and take inventory. In one sitting, with one tool, you've got:

  • A working Go installation (the current version, not whatever Ubuntu felt like packaging)
  • A project using the modern module system — no GOPATH required
  • A running program
  • An external dependency, fetched and pinned
  • Editor support with autocomplete, inline errors, and automatic formatting

That's the whole development environment. No virtual environments. No requirements.txt vs. setup.py debates. No separate formatter to install and configure. No type checker requiring its own setup. The Go toolchain ships as a single, coherent unit, and this deliberate design decision is why Go feels productive almost immediately, once you get past the syntax learning curve.

Next we dig into the actual code. Types, variables, and all the things Python let you ignore but Go demands you be explicit about. The setup was easy — the syntax has more texture. But it'll click faster than you expect.