net/go.book
All Parts Marketing

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/project in any folder.
  • Your first program: main.go with package main and func main().
  • Editor: VS Code with the Go extension, or GoLand, or Vim with gopls.

Multiple Go installations fighting over your PATH
Installing Go via a package manager (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()
    }
}

Ignoring errors from Listen and Accept
The example discards errors with _ — 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.