Setting Up Your Go Development Environment
If you completed Go Fundamentals (the section immediately before this one), your Go toolchain is already installed, your editor is configured, and you have written a dozen programs — feel free to skip to Chapter 3. This chapter is a condensed setup reference for readers who started here directly.
Quick Setup Checklist
- Install Go: golang.org/dl. Verify with
go version. - Create a module:
go mod init github.com/you/projectin any folder. - Your first program:
main.gowithpackage mainandfunc main(). - Editor: VS Code with the Go extension, or GoLand, or Vim with
gopls.
apt, brew) and from golang.org can leave two go binaries on your PATH. If go version reports an unexpected version, run which go (macOS/Linux) or where go (Windows) to find the stale one.Your First Networking Server
With Go installed and a module initialized, here is a TCP server in under 15 lines:
package main
import (
"fmt"
"net"
)
func main() {
ln, _ := net.Listen("tcp", ":8080")
fmt.Println("Listening on :8080...")
for {
conn, _ := ln.Accept()
fmt.Fprintln(conn, "Hello from Go server!")
conn.Close()
}
}
_ — fine for a demo, but dangerous in production. If Listen fails, ln is nil and the program panics on ln.Accept() far from the real cause.A hardened version with error checks and concurrent handling:
package main
import (
"fmt"
"log"
"net"
)
func main() {
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("listen failed: ", err)
}
defer ln.Close()
fmt.Println("Listening on :8080...")
for {
conn, err := ln.Accept()
if err != nil {
log.Println("accept error:", err)
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
fmt.Fprintln(conn, "Hello from Go server!")
}
go handleConn(conn) spawns a goroutine per client — the accept loop never blocks waiting for a slow client. This is the fundamental shape reused for every TCP server throughout the book.
A listener that only ever handles one client at a time isn't a network server yet — it's a very polite queue.
Try It Yourself: Echo Server
Replace handleConn with an echo loop:
func handleConn(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
return
}
conn.Write(buf[:n])
}
}
Run the server, connect with nc localhost 8080, type anything — it echoes back.
Go Version Timeline
| Version | Year | Major Features |
|---|---|---|
| 1.0 | 2012 | First stable release, goroutines, channels |
| 1.5 | 2015 | Compiler written in Go, improved GC |
| 1.11 | 2018 | Go modules |
| 1.13 | 2019 | Error wrapping |
| 1.18 | 2022 | Generics |
| 1.22 | 2024 | Method-based HTTP routing, loop variable fix |
| 1.24 | 2025 | Generic type aliases, omitzero in encoding/json |
Frequently Asked Questions
Do I still need to set a GOPATH before I can start coding?
No. Before modules (Go 1.11), every project had to live inside $GOPATH/src/.... Since go mod init works in any folder, GOPATH today is just where the module cache and installed binaries live — not where your source code sits.
My editor says "no packages found for open files" — did I install Go wrong?
Almost certainly not. Tools like gopls need to see a go.mod to understand your project. Open the folder containing go.mod as your workspace root, not a subfolder inside it.
Why does the server loop forever with for { ln.Accept() } instead of handling one connection and exiting?
Because a network server's job is to keep waiting for the next client. Accept() blocks until a connection arrives, returns one net.Conn, and the loop sends the listener straight back to waiting — paired with go handleConn(conn), this is the standard Go server shape: listen once, loop forever, handle concurrently.