Table of Contents
- Standard Library Essentials
- Contents
- net/http server
- net/http client
- encoding/json
- database/sql
- io & bufio
- time
- context
- log/slog
- flag
- os/exec
Standard Library Essentials¶
Contents¶
- net/http server
- net/http client
- encoding/json
- database/sql
- io & bufio
- time
- context
- log/slog
- flag
- os/exec
net/http server¶
import "net/http"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser) // method + path wildcards (Go 1.22+)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
io.WriteString(w, "ok")
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+ path wildcard
q := r.URL.Query().Get("verbose") // ?verbose=...
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id})
}
// Middleware = func(http.Handler) http.Handler
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
// srv.Handler = logging(mux)
Graceful shutdown:
go srv.ListenAndServe()
<-ctx.Done() // e.g. on SIGINT
shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(shutCtx)
net/http client¶
Don't use http.Get for production — create a client with timeouts and always close the body.
client := &http.Client{Timeout: 10 * time.Second}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil { return err }
defer resp.Body.Close() // ALWAYS, even on non-2xx
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status %d", resp.StatusCode)
}
var out Result
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return err
}
// POST JSON:
body, _ := json.Marshal(payload)
resp, err = client.Post(url, "application/json", bytes.NewReader(body))
encoding/json¶
import "encoding/json"
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // omit when zero
pass string `json:"-"` // unexported never marshaled anyway
}
// Marshal / Unmarshal (whole value in memory)
b, err := json.Marshal(u) // []byte
b, _ = json.MarshalIndent(u, "", " ") // pretty
err = json.Unmarshal(b, &u) // pass a pointer
// Streaming (preferred for I/O)
json.NewEncoder(w).Encode(u) // adds trailing newline
json.NewDecoder(r.Body).Decode(&u)
// Arbitrary / unknown shape
var data map[string]any
json.Unmarshal(b, &data) // numbers decode to float64!
// Decoder options
dec := json.NewDecoder(r)
dec.DisallowUnknownFields() // strict
dec.UseNumber() // numbers as json.Number, not float64
// Custom marshaling
func (u User) MarshalJSON() ([]byte, error) { ... }
func (u *User) UnmarshalJSON(b []byte) error { ... }
// RawMessage delays parsing of a sub-field
type Envelope struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
Gotchas: only exported fields marshal; any numbers become float64; nil slice -> null, empty slice -> []; time.Time marshals as RFC3339.
database/sql¶
import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib" // driver, blank import registers it
)
db, err := sql.Open("pgx", dsn) // doesn't connect yet
if err != nil { log.Fatal(err) }
defer db.Close()
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)
if err := db.PingContext(ctx); err != nil { // actually connects
log.Fatal(err)
}
// Query a single row
var name string
err = db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", id).Scan(&name)
if errors.Is(err, sql.ErrNoRows) { /* not found */ }
// Query many rows
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users")
if err != nil { return err }
defer rows.Close()
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name); err != nil { return err }
users = append(users, u)
}
if err := rows.Err(); err != nil { return err } // check iteration error!
// Exec (INSERT/UPDATE/DELETE)
res, err := db.ExecContext(ctx, "UPDATE users SET name=$1 WHERE id=$2", name, id)
n, _ := res.RowsAffected()
// Transaction
tx, err := db.BeginTx(ctx, nil)
if err != nil { return err }
defer tx.Rollback() // no-op if already committed
if _, err := tx.ExecContext(ctx, "..."); err != nil { return err }
return tx.Commit()
Always use placeholders ($1/?) — never string-concatenate SQL. Always defer rows.Close() and check rows.Err().
io & bufio¶
import ("io"; "bufio"; "os")
io.Copy(dst, src) // stream copy
io.ReadAll(r) // read everything ([]byte)
io.WriteString(w, "text")
io.Discard // /dev/null writer
io.MultiReader(r1, r2); io.MultiWriter(w1, w2)
io.LimitReader(r, 1<<20) // cap bytes read
io.TeeReader(r, logWriter) // copy reads to a side writer
// Buffered line scanning (default token = line)
f, _ := os.Open("file.txt")
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 1024), 1024*1024) // raise max line size
for sc.Scan() {
line := sc.Text() // or sc.Bytes()
_ = line
}
if err := sc.Err(); err != nil { /* handle */ }
sc.Split(bufio.ScanWords) // word/rune/byte tokenizers
// Buffered writer — remember to Flush
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
fmt.Fprintln(w, "buffered")
r := bufio.NewReader(conn)
line, err := r.ReadString('\n')
time¶
import "time"
now := time.Now()
now.Unix() // seconds since epoch
now.UTC(); now.Local()
now.Format("2006-01-02 15:04:05") // REFERENCE TIME layout (Mon Jan 2 15:04:05 MST 2006)
t, err := time.Parse(time.RFC3339, "2026-06-26T10:00:00Z")
t, err = time.ParseInLocation("2006-01-02", s, loc)
d := 90 * time.Minute
d.Hours() // 1.5
time.Since(start) // elapsed (shortcut for Now().Sub)
time.Until(deadline)
now.Add(24 * time.Hour)
now.AddDate(0, 1, 0) // +1 month
now.Sub(other) // Duration
now.Before(t); now.After(t); now.Equal(t)
now.Truncate(time.Hour); now.Round(time.Minute)
// Timers & tickers — stop them to avoid leaks
timer := time.NewTimer(2 * time.Second)
<-timer.C
timer.Stop()
tick := time.NewTicker(time.Second)
defer tick.Stop()
for range tick.C { doPeriodic() }
<-time.After(time.Second) // one-off (used in select)
time.Sleep(100 * time.Millisecond)
The magic layout string is 2006-01-02 15:04:05 (Mon=1, Jan=1, 2pm=15...). Memorize it: 01/02 03:04:05PM '06 -0700.
context¶
See concurrency.md for full detail.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
row := db.QueryRowContext(ctx, sql, args...)
select {
case <-ctx.Done():
return ctx.Err()
case r := <-ch:
return r
}
log/slog¶
Structured logging, stdlib since Go 1.21.
import "log/slog"
slog.Info("server started", "port", 8080, "env", "prod")
slog.Error("query failed", "err", err, "id", id)
slog.Warn("retrying", slog.Int("attempt", 3))
// JSON handler + level control
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})
logger := slog.New(h)
slog.SetDefault(logger) // make it the package default
// Contextual child loggers
reqLog := logger.With("request_id", id, "user", uid)
reqLog.Info("handling")
// Groups and typed attrs
logger.Info("io", slog.Group("file", "name", n, "size", sz))
// Context-aware
logger.InfoContext(ctx, "msg")
Old log package still fine for simple cases: log.Printf, log.Fatal (logs then os.Exit(1)), log.Panic.
flag¶
import "flag"
var (
port = flag.Int("port", 8080, "listen port")
verbose = flag.Bool("v", false, "verbose output")
name = flag.String("name", "world", "who to greet")
)
func main() {
flag.Parse() // parse os.Args
fmt.Println(*port, *verbose, *name)
args := flag.Args() // remaining positional args
_ = args
}
// Bind into existing variables:
var cfg Config
flag.StringVar(&cfg.Host, "host", "localhost", "host")
// Custom flag set (e.g. for subcommands):
fs := flag.NewFlagSet("serve", flag.ExitOnError)
fs.Int("workers", 4, "worker count")
fs.Parse(os.Args[2:])
Usage: ./app -port 9090 -v file1 file2. -h/--help auto-prints usage.
os/exec¶
import "os/exec"
// Run and capture combined output
out, err := exec.Command("git", "rev-parse", "HEAD").Output() // stdout only
out, err = exec.Command("ls", "-la").CombinedOutput() // stdout+stderr
// With context (timeout/cancel) — preferred
cmd := exec.CommandContext(ctx, "sleep", "10")
err = cmd.Run() // killed when ctx cancels
// Fine-grained control
cmd = exec.Command("grep", "foo")
cmd.Stdin = strings.NewReader("foo\nbar\n")
var stdout, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &stdout, &stderr
cmd.Env = append(os.Environ(), "LANG=C")
cmd.Dir = "/tmp"
if err := cmd.Run(); err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
fmt.Println("exit code:", ee.ExitCode(), stderr.String())
}
}
// Streaming with pipes
cmd = exec.Command("long-running")
stdoutPipe, _ := cmd.StdoutPipe()
cmd.Start()
io.Copy(os.Stdout, stdoutPipe)
cmd.Wait()
// Resolve a binary in PATH
path, err := exec.LookPath("docker")
Run = Start + Wait. Output/CombinedOutput capture; raw Command inherits nothing unless you wire Stdout/Stderr.