net/http Basics
9 examples to get you started with net/http - 7 basic and 2 intermediate.
Search across all documentation pages
9 examples to get you started with net/http - 7 basic and 2 intermediate.
mkdir httplab && cd httplab && go mod init example.com/httplab.main.go (or separate files in one package) and run with go run ..A function with signature func(http.ResponseWriter, *http.Request) can serve HTTP directly.
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello")
})
http.ListenAndServe(":8080", nil)
}http.HandleFunc registers on http.DefaultServeMux.nil to ListenAndServe uses that default mux.ServeMux in real services to avoid global route collisions.Related: net/http: Go's Batteries-Included HTTP Stack - conceptual overview
Isolate routes in a mux you own instead of the global default.
package main
import (
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
http.ListenAndServe(":8080", mux)
}http.NewServeMux() returns an empty router.ListenAndServe.Related: HTTP Servers, Handlers & ServeMux Patterns - Go 1.22+ patterns
Register routes with HTTP method and {param} segments in one call.
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "user=%s", r.PathValue("id"))
})
http.ListenAndServe(":8080", mux)
}"METHOD /path/{name}" on Go 1.22 and later.r.PathValue("id") reads captured segments.Related: HTTP Servers, Handlers & ServeMux Patterns - routing patterns
Set headers before writing the body; the first Write sends headers implicitly.
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
w.Write([]byte(`{"ok":true}`))
})
http.ListenAndServe(":8080", nil)
}WriteHeader only once per response.Header().Set is case-canonicalized by net/http.http.Error for quick plain-text error responses.Decode JSON from r.Body and always close what you open on the client side.
package main
import (
"encoding/json"
"io"
"net/http"
)
type Payload struct {
Name string `json:"name"`
}
func main() {
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
var p Payload
if err := json.NewDecoder(r.Body).Decode(&p); err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(p)
})
http.ListenAndServe(":8080", nil)
}r.Body must be read or closed to reuse keep-alive connections.http.MaxBytesReader in production handlers.json.NewDecoder for streaming large payloads.Outbound calls should use a client with timeout and context cancellation.
package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func main() {
client := &http.Client{Timeout: 5 * time.Second}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Status, len(b))
}http.Get in servers; it uses http.DefaultClient without timeout.defer resp.Body.Close() to return connections to the pool.NewRequestWithContext ties the call to caller cancellation.Related: HTTP Clients, Transport & Connection Pooling - transport tuning
Middleware wraps an inner handler and runs code before or after it.
package main
import (
"log"
"net/http"
"time"
)
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))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/work", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("done"))
})
http.ListenAndServe(":8080", logging(mux))
}func(http.Handler) http.Handler.ListenAndServe, not the bare inner handler.Related: Middleware Chains & Request Context - chains and values
Configure timeouts on http.Server instead of bare ListenAndServe.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("api"))
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(ctx)
}ReadHeaderTimeout mitigates slowloris-style header attacks.Shutdown drains in-flight requests during deploys.main can handle signals.Related: Production net/http Configuration Checklist - full production list
Test handlers in memory without opening a TCP port.
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func greet(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
}
func TestGreet(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/greet", nil)
rec := httptest.NewRecorder()
greet(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d", rec.Code)
}
if rec.Body.String() != "hi" {
t.Fatalf("body=%q", rec.Body.String())
}
}httptest.NewRecorder implements http.ResponseWriter and records status/body.httptest.NewRequest builds a *http.Request with optional body.Related: httptest for Handler Integration Tests - fuller testing patterns
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026