The standard library toolkit
strings and strconv, time, the slices and maps helpers, encoding/json, flag, log/slog, os and io/fs, and a first net/http server.
Text, numbers and time
import (
"slices"
"strconv"
"strings"
"time"
)
label := strings.TrimSpace(" Hello, cafe ")
parts := strings.Split("a,b,c", ",")
joined := strings.Join(parts, " | ")
has := strings.Contains(strings.ToLower(label), "hello")
n, err := strconv.Atoi("42") // string -> int
s := strconv.Itoa(7) // int -> string
f, _ := strconv.ParseFloat("3.5", 64)
b, _ := strconv.ParseBool("true")
// time: never format with anything but the reference layout
now := time.Now().UTC()
stamp := now.Format(time.RFC3339) // 2026-09-18T09:30:00Z
t, err := time.Parse(time.RFC3339, stamp)
later := t.Add(90 * time.Minute)
d := later.Sub(t) // a Duration, not a timestamp
// the zero-value helpers
nums := []int{3, 1, 2}
slices.Sort(nums)
i, found := slices.BinarySearch(nums, 2)
nums = slices.Compact(slices.Clone([]int{1, 1, 2}))| Package | Remember this |
|---|---|
strings | Trim, Split, Join, Cut, HasPrefix, ReplaceAll; everything returns new strings, since strings are immutable |
strconv | The string-to-number boundary. Atoi and ParseInt return errors you must check |
time | Layouts are reference times, not format strings. Durations are time.Duration; always store UTC and convert at the edge |
slices / maps | Sort, BinarySearch, Contains, Compact, Clone, Keys, Values — replaces most hand-written loops |
encoding/json | Struct tags drive field names; omitempty and - are the two you will use constantly |
log/slog | Structured logging with levels and key-value pairs; the default handler is text, NewJSONHandler for production |
JSON, files and flags
type Config struct {
Name string `json:"name"`
Port int `json:"port"`
Debug bool `json:"debug,omitempty"`
Key string `json:"-"` // never encoded
}
func load(path string) (Config, error) {
var c Config
data, err := os.ReadFile(path)
if err != nil {
return c, err
}
// Decoder is preferred over Unmarshal: it reports trailing garbage
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&c); err != nil {
return c, fmt.Errorf("parse %s: %w", path, err)
}
return c, nil
}
func main() {
verbose := flag.Bool("v", false, "verbose output")
flag.Parse() // flag.Var and flag.Func for custom types
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
logger.Info("starting", "verbose", *verbose, "pid", os.Getpid())
if err := run(logger); err != nil {
logger.Error("fatal", "err", err) // structured, not string concatenation
os.Exit(1)
}
}- Use
json.NewEncoder(w).Encode(v)when the output target is anio.Writersuch as a file or response body — it streams instead of building one large buffer. - Use
json.NewDecoder(r).Decode(&v)on input so trailing data and malformed bodies are detected. - Field names must be exported to be encoded. A lowercase field is silently skipped, which is the most common cause of an empty JSON object.
- Pass the logger as an explicit parameter, or store it in a struct. Package-level loggers make tests noisy and are hard to reconfigure.
- Use
os.ReadFilefor small whole files andbufio.Scannerorbufio.Readerfor large or line-oriented ones.io/fsalso lets you test file code against an in-memory filesystem.
A first HTTP server
package main
import (
"encoding/json"
"log/slog"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok")) // 200 and a small body
})
mux.HandleFunc("GET /v1/items/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // the path wildcard
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id})
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second, // protects against slow clients
}
slog.Info("listening", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil {
slog.Error("server stopped", "err", err)
}
}⚠️
Every
http.Server in production needs ReadHeaderTimeout and WriteTimeout. Without them a handful of idle connections can hold resources indefinitely, and ListenAndServe returns http.ErrServerClosed on a clean shutdown — do not treat that value as a failure.FAQ
Why is my JSON output empty?
Almost always the fields are unexported. Only fields starting with a capital letter are encoded; add a struct tag to control the wire name.
How do I format a date in Go?
Use the reference layout
time.RFC3339 or a layout built from the reference time Mon Jan 2 15:04:05 MST 2006. There is no yyyy-MM-dd pattern language.Related
Building HTTP services Setting up Go: modules, toolchain and editors
Last refreshed 2026-09-18.